Propagate modprinc -unlock from master to slave KDCs
authorGreg Hudson <ghudson@mit.edu>
Tue, 5 Oct 2010 14:53:09 +0000 (14:53 +0000)
committerGreg Hudson <ghudson@mit.edu>
Tue, 5 Oct 2010 14:53:09 +0000 (14:53 +0000)
Create a new tl-data type to hold the time of the last administrative
unlock, and factor it into decisions about account lockout.  Since
tl-data values are propagated from master to slave, this will cause
modprinc -unlock operations to reach slave KDCs on the next
propagation.

ticket: 6795

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@24424 dc483132-0cff-0310-8789-dd5450dbe970

14 files changed:
src/include/kdb.h
src/kadmin/cli/kadmin.c
src/lib/kdb/kdb5.c
src/lib/kdb/libkdb5.exports
src/plugins/kdb/db2/lockout.c
src/plugins/kdb/ldap/libkdb_ldap/kerberos.ldif
src/plugins/kdb/ldap/libkdb_ldap/kerberos.schema
src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c
src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c
src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h
src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
src/plugins/kdb/ldap/libkdb_ldap/ldap_service_rights.c
src/plugins/kdb/ldap/libkdb_ldap/lockout.c
src/tests/t_lockout.py

index dac44ea551588c04886bcfcda47b4150b027748c..45622791776f13702ccc6b024e12950e7f4cabb1 100644 (file)
@@ -238,6 +238,7 @@ typedef struct __krb5_key_salt_tuple {
 #define KRB5_TL_CONSTRAINED_DELEGATION_ACL 0x0400 /* Each entry is a permitted SPN */
 #define KRB5_TL_LM_KEY                  0x0500 /* LM OWF */
 #define KRB5_TL_X509_SUBJECT_ISSUER_NAME 0x0600 /* <I>IssuerDN<S>SubjectDN */
+#define KRB5_TL_LAST_ADMIN_UNLOCK       0x0700 /* Timestamp of admin unlock */
 
 /* version number for KRB5_TL_ACTKVNO data */
 #define KRB5_TL_ACTKVNO_VER     1
@@ -493,6 +494,11 @@ krb5_dbe_update_last_pwd_change( krb5_context     context,
                                  krb5_db_entry  * entry,
                                  krb5_timestamp   stamp);
 
+krb5_error_code
+krb5_dbe_update_last_admin_unlock( krb5_context     context,
+                                   krb5_db_entry  * entry,
+                                   krb5_timestamp   stamp);
+
 krb5_error_code
 krb5_dbe_lookup_tl_data( krb5_context          context,
                          krb5_db_entry       * entry,
@@ -522,6 +528,11 @@ krb5_dbe_lookup_last_pwd_change( krb5_context          context,
                                  krb5_db_entry       * entry,
                                  krb5_timestamp      * stamp);
 
+krb5_error_code
+krb5_dbe_lookup_last_admin_unlock( krb5_context          context,
+                                   krb5_db_entry       * entry,
+                                   krb5_timestamp      * stamp);
+
 krb5_error_code
 krb5_dbe_delete_tl_data( krb5_context    context,
                          krb5_db_entry * entry,
index 9e5bc44796367ddfd1d3045bcb5de6e55b51ce83..ff6eeca6b7e63f148f5e7607f6c981141d23d54a 100644 (file)
@@ -815,6 +815,55 @@ kadmin_free_tl_data(kadm5_principal_ent_t princ)
     }
 }
 
+/* Construct a tl_data element and add it to the tail of princ->tl_data. */
+static void
+add_tl_data(kadm5_principal_ent_t princ, krb5_int16 tl_type, krb5_ui_2 len,
+            krb5_octet *contents)
+{
+    krb5_tl_data *tl_data, **tlp;
+    krb5_octet *copy;
+
+    copy = malloc(len);
+    tl_data = calloc(1, sizeof(*tl_data));
+    if (copy == NULL || tl_data == NULL) {
+        fprintf(stderr, "Not enough memory\n");
+        exit(1);
+    }
+    memcpy(copy, contents, len);
+
+    tl_data->tl_data_type = tl_type;
+    tl_data->tl_data_length = len;
+    tl_data->tl_data_contents = copy;
+    tl_data->tl_data_next = NULL;
+
+    for (tlp = &princ->tl_data; *tlp != NULL; tlp = &(*tlp)->tl_data_next);
+    *tlp = tl_data;
+    princ->n_tl_data++;
+}
+
+static void
+unlock_princ(kadm5_principal_ent_t princ, long *mask, const char *caller)
+{
+    krb5_error_code retval;
+    krb5_timestamp now;
+    krb5_octet timebuf[4];
+
+    /* Zero out the failed auth count. */
+    princ->fail_auth_count = 0;
+    *mask |= KADM5_FAIL_AUTH_COUNT;
+
+    /* Record the timestamp of this unlock operation so that slave KDCs will
+     * see it, since fail_auth_count is unreplicated. */
+    retval = krb5_timeofday(context, &now);
+    if (retval) {
+        com_err(caller, retval, "while getting time");
+        exit(1);
+    }
+    store_32_le((krb5_int32)now, timebuf);
+    add_tl_data(princ, KRB5_TL_LAST_ADMIN_UNLOCK, 4, timebuf);
+    *mask |= KADM5_TL_DATA;
+}
+
 /*
  * Parse addprinc or modprinc arguments.  Some output fields may be
  * filled in on error.
@@ -834,7 +883,6 @@ kadmin_parse_princ_args(int argc, char *argv[], kadm5_principal_ent_t oprinc,
     time_t date;
     time_t now;
     krb5_error_code retval;
-    krb5_tl_data *tl_data, *tail = NULL;
 
     *mask = 0;
     *pass = NULL;
@@ -851,29 +899,8 @@ kadmin_parse_princ_args(int argc, char *argv[], kadm5_principal_ent_t oprinc,
             if (++i > argc - 2)
                 return -1;
 
-            tl_data = malloc(sizeof(krb5_tl_data));
-            if (tl_data == NULL) {
-                fprintf(stderr, "Not enough memory\n");
-                exit(1);
-            }
-
-            memset(tl_data, 0, sizeof(krb5_tl_data));
-            tl_data->tl_data_type = KRB5_TL_DB_ARGS;
-            tl_data->tl_data_length = strlen(argv[i])+1;
-            tl_data->tl_data_contents = (krb5_octet *) strdup(argv[i]);
-
-            if (tail) {
-                tail->tl_data_next = tl_data;
-            } else {
-                oprinc->tl_data = tl_data;
-            }
-            tail = tl_data;
-            oprinc->n_tl_data++;
-
-            if (tl_data->tl_data_contents == NULL) {
-                fprintf(stderr, "Not enough memory\n");
-                exit(1);
-            }
+            add_tl_data(oprinc, KRB5_TL_DB_ARGS, strlen(argv[i]) + 1,
+                        (krb5_octet *)argv[i]);
             *mask |= KADM5_TL_DATA;
             continue;
         }
@@ -983,8 +1010,7 @@ kadmin_parse_princ_args(int argc, char *argv[], kadm5_principal_ent_t oprinc,
         }
 #endif /* APPLE_PKINIT */
         if (strlen(argv[i]) == 7 && !strcmp("-unlock", argv[i])) {
-            oprinc->fail_auth_count = 0;
-            *mask |= KADM5_FAIL_AUTH_COUNT;
+            unlock_princ(oprinc, mask, caller);
             continue;
         }
         if (!strcmp("-e", argv[i])) {
index 5e00d127ee047e18ca0863facb2ac91d45622ecb..b37a5c1102a66d091eec5d7151a227b624cc22ac 100644 (file)
@@ -1430,6 +1430,31 @@ krb5_dbe_lookup_last_pwd_change(krb5_context context, krb5_db_entry *entry,
     return (0);
 }
 
+krb5_error_code
+krb5_dbe_lookup_last_admin_unlock(krb5_context context, krb5_db_entry *entry,
+                                  krb5_timestamp *stamp)
+{
+    krb5_tl_data tl_data;
+    krb5_error_code code;
+    krb5_int32 tmp;
+
+    tl_data.tl_data_type = KRB5_TL_LAST_ADMIN_UNLOCK;
+
+    if ((code = krb5_dbe_lookup_tl_data(context, entry, &tl_data)))
+        return (code);
+
+    if (tl_data.tl_data_length != 4) {
+        *stamp = 0;
+        return (0);
+    }
+
+    krb5_kdb_decode_int32(tl_data.tl_data_contents, tmp);
+
+    *stamp = (krb5_timestamp) tmp;
+
+    return (0);
+}
+
 krb5_error_code
 krb5_dbe_lookup_tl_data(krb5_context context, krb5_db_entry *entry,
                         krb5_tl_data *ret_tl_data)
@@ -1921,6 +1946,21 @@ krb5_dbe_update_last_pwd_change(krb5_context context, krb5_db_entry *entry,
     return (krb5_dbe_update_tl_data(context, entry, &tl_data));
 }
 
+krb5_error_code
+krb5_dbe_update_last_admin_unlock(krb5_context context, krb5_db_entry *entry,
+                                  krb5_timestamp stamp)
+{
+    krb5_tl_data tl_data;
+    krb5_octet buf[4];          /* this is the encoded size of an int32 */
+
+    tl_data.tl_data_type = KRB5_TL_LAST_ADMIN_UNLOCK;
+    tl_data.tl_data_length = sizeof(buf);
+    krb5_kdb_encode_int32((krb5_int32) stamp, buf);
+    tl_data.tl_data_contents = buf;
+
+    return (krb5_dbe_update_tl_data(context, entry, &tl_data));
+}
+
 krb5_error_code
 krb5_dbe_delete_tl_data(krb5_context context, krb5_db_entry *entry,
                         krb5_int16 tl_data_type)
index f3f6a8021c37bebf8906a03b6f8335ac4c08146d..f9f66eba2176cbbca53962e38670ed93ca22413a 100644 (file)
@@ -44,6 +44,7 @@ krb5_dbe_free_key_data_contents
 krb5_dbe_free_mkey_aux_list
 krb5_dbe_free_key_list
 krb5_dbe_get_mkvno
+krb5_dbe_lookup_last_admin_unlock
 krb5_dbe_lookup_last_pwd_change
 krb5_dbe_lookup_actkvno
 krb5_dbe_lookup_mkey_aux
@@ -52,6 +53,7 @@ krb5_dbe_lookup_mod_princ_data
 krb5_dbe_lookup_tl_data
 krb5_dbe_search_enctype
 krb5_dbe_update_actkvno
+krb5_dbe_update_last_admin_unlock
 krb5_dbe_update_last_pwd_change
 krb5_dbe_update_mkey_aux
 krb5_dbe_update_mkvno
index 3ba53d214c96294f900d2702c5bbbe966065b195..b473611244b9d0a9b55a221d9ca73f1d4cfcd6da 100644 (file)
@@ -100,6 +100,13 @@ locked_check_p(krb5_context context,
                krb5_timestamp lockout_duration,
                krb5_db_entry *entry)
 {
+    krb5_timestamp unlock_time;
+
+    /* If the entry was unlocked since the last failure, it's not locked. */
+    if (krb5_dbe_lookup_last_admin_unlock(context, entry, &unlock_time) == 0 &&
+        entry->last_failed <= unlock_time)
+        return FALSE;
+
     if (max_fail == 0 || entry->fail_auth_count < max_fail)
         return FALSE;
 
@@ -147,6 +154,7 @@ krb5_db2_lockout_audit(krb5_context context,
     krb5_deltat lockout_duration = 0;
     krb5_db2_context *db_ctx = context->dal_handle->db_context;
     krb5_boolean need_update = FALSE;
+    krb5_timestamp unlock_time;
 
     switch (status) {
     case 0:
@@ -182,6 +190,13 @@ krb5_db2_lockout_audit(krb5_context context,
     } else if (!db_ctx->disable_lockout &&
                (status == KRB5KDC_ERR_PREAUTH_FAILED ||
                 status == KRB5KRB_AP_ERR_BAD_INTEGRITY)) {
+        if (krb5_dbe_lookup_last_admin_unlock(context, entry,
+                                              &unlock_time) == 0 &&
+            entry->last_failed <= unlock_time) {
+            /* Reset fail_auth_count after administrative unlock. */
+            entry->fail_auth_count = 0;
+        }
+
         if (failcnt_interval != 0 &&
             stamp > entry->last_failed + failcnt_interval) {
             /* Reset fail_auth_count after failcnt_interval. */
index 886f8b4359c850c0a2b97e8472c7a22de368faad..33a2365641812ec26795e2301d142ed149165a43 100644 (file)
@@ -550,6 +550,16 @@ attributetypes: ( 2.16.840.1.113719.1.301.4.45.1
                 SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
                 SINGLE-VALUE)
 
+##### The time at which the principal was last administratively unlocked.
+
+dn: cn=schema
+changetype: modify
+add: attributetypes
+attributetypes: ( 1.3.6.1.4.1.5322.21.2.5
+                NAME 'krbLastAdminUnlock'
+                EQUALITY generalizedTimeMatch
+                SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+                SINGLE-VALUE)
 
 ##### This attribute holds the kerberos master key.
 ##### This can be used to encrypt principal keys. 
@@ -754,7 +764,7 @@ add: objectclasses
 objectClasses: ( 2.16.840.1.113719.1.301.6.8.1
                 NAME 'krbPrincipalAux'
                 AUXILIARY
-                MAY ( krbPrincipalName $ krbCanonicalName $ krbUPEnabled $ krbPrincipalKey $ krbTicketPolicyReference $ krbPrincipalExpiration $ krbPasswordExpiration $ krbPwdPolicyReference $ krbPrincipalType $ krbPwdHistory $ krbLastPwdChange $ krbPrincipalAliases $ krbLastSuccessfulAuth $ krbLastFailedAuth $ krbLoginFailedCount $ krbExtraData $ krbAllowedToDelegateTo ) )
+                MAY ( krbPrincipalName $ krbCanonicalName $ krbUPEnabled $ krbPrincipalKey $ krbTicketPolicyReference $ krbPrincipalExpiration $ krbPasswordExpiration $ krbPwdPolicyReference $ krbPrincipalType $ krbPwdHistory $ krbLastPwdChange $ krbLastAdminUnlock $ krbPrincipalAliases $ krbLastSuccessfulAuth $ krbLastFailedAuth $ krbLoginFailedCount $ krbExtraData $ krbAllowedToDelegateTo ) )
 
 
 ###### This class is used to create additional principals and stand alone principals.
index 65e07d6cde85effe054ce46e02b352e9cc3726ce..f4f8aca3ffeedf1db405fb365d7494481be3256c 100644 (file)
@@ -445,6 +445,13 @@ attributetype ( 2.16.840.1.113719.1.301.4.45.1
                 SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
                 SINGLE-VALUE)
 
+##### The time at which the principal was last administratively unlocked.
+
+attributetype ( 1.3.6.1.4.1.5322.21.2.5
+                NAME 'krbLastAdminUnlock'
+                EQUALITY generalizedTimeMatch
+                SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+                SINGLE-VALUE)
 
 ##### This attribute holds the kerberos master key.
 ##### This can be used to encrypt principal keys. 
@@ -609,7 +616,7 @@ objectclass ( 2.16.840.1.113719.1.301.6.8.1
                 NAME 'krbPrincipalAux'
                SUP top
                 AUXILIARY
-                MAY ( krbPrincipalName $ krbCanonicalName $ krbUPEnabled $ krbPrincipalKey $ krbTicketPolicyReference $ krbPrincipalExpiration $ krbPasswordExpiration $ krbPwdPolicyReference $ krbPrincipalType $ krbPwdHistory $ krbLastPwdChange $ krbPrincipalAliases $ krbLastSuccessfulAuth $ krbLastFailedAuth $ krbLoginFailedCount $ krbExtraData $ krbAllowedToDelegateTo ) )
+                MAY ( krbPrincipalName $ krbCanonicalName $ krbUPEnabled $ krbPrincipalKey $ krbTicketPolicyReference $ krbPrincipalExpiration $ krbPasswordExpiration $ krbPwdPolicyReference $ krbPrincipalType $ krbPwdHistory $ krbLastPwdChange $ krbLastAdminUnlock $ krbPrincipalAliases $ krbLastSuccessfulAuth $ krbLastFailedAuth $ krbLoginFailedCount $ krbExtraData $ krbAllowedToDelegateTo ) )
 
 
 ###### This class is used to create additional principals and stand alone principals.
index e9d50196dec46a694578da85e8fa862bb182e504..86fa4d1e5e9ac2027d046ef12690ecc10edc5ccf 100644 (file)
@@ -1997,6 +1997,20 @@ populate_krb5_db_entry(krb5_context context, krb5_ldap_context *ldap_context,
         }
     }
 
+    /* LAST ADMIN UNLOCK */
+    {
+        krb5_timestamp unlock_time=0;
+        if ((st=krb5_ldap_get_time(ld, ent, "krbLastAdminUnlock",
+                                   &unlock_time, &attr_present)) != 0)
+            goto cleanup;
+        if (attr_present == TRUE) {
+            if ((st=krb5_dbe_update_last_admin_unlock(context, entry,
+                                                      unlock_time)))
+                goto cleanup;
+            mask |= KDB_LAST_ADMIN_UNLOCK_ATTR;
+        }
+    }
+
     /* ALLOWED TO DELEGATE TO */
     {
         char **a2d2 = NULL;
index 68b08c51224f9f9ca782514605ce40713641ffc5..0805019db3c21c310bd8c8ba317c76d6558abfb8 100644 (file)
@@ -60,6 +60,7 @@ char     *principal_attributes[] = { "krbprincipalname",
                                      "logindisabled",
 #endif
                                      "krbLastPwdChange",
+                                     "krbLastAdminUnlock",
                                      "krbExtraData",
                                      "krbObjectReferences",
                                      "krbAllowedToDelegateTo",
@@ -80,6 +81,7 @@ static char *attributes_set[] = { "krbmaxticketlife",
                                   "krbLastSuccessfulAuth",
                                   "krbLastFailedAuth",
                                   "krbLoginFailedCount",
+                                  "krbLastAdminUnlock",
                                   NULL };
 
 void
index 3942e3f4208f531159001d048b257c3111446d63..7a7a8067fd8b8c67c02de113aacd1eccde8cecd7 100644 (file)
@@ -82,7 +82,7 @@
 #define KDB_LAST_SUCCESS_ATTR                0x000800
 #define KDB_LAST_FAILED_ATTR                 0x001000
 #define KDB_FAIL_AUTH_COUNT_ATTR             0x002000
-#define KDB_LOCKED_TIME_ATTR                 0x004000
+#define KDB_LAST_ADMIN_UNLOCK_ATTR           0x004000
 
 /*
  * This is a private contract between krb5_ldap_lockout_audit()
index 27a3a644104366a48e69508006ebf6aec4a83435..140db1a476a16db6b275adf7b0690875f368b334 100644 (file)
@@ -1051,6 +1051,7 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
         int count = 0;
         struct berval **ber_tl_data = NULL;
         krb5_tl_data *ptr;
+        krb5_timestamp unlock_time;
         for (ptr = entry->tl_data; ptr != NULL; ptr = ptr->tl_data_next) {
             if (ptr->tl_data_type == KRB5_TL_LAST_PWD_CHANGE
 #ifdef SECURID
@@ -1058,7 +1059,8 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
 #endif
                 || ptr->tl_data_type == KRB5_TL_KADM_DATA
                 || ptr->tl_data_type == KDB_TL_USER_INFO
-                || ptr->tl_data_type == KRB5_TL_CONSTRAINED_DELEGATION_ACL)
+                || ptr->tl_data_type == KRB5_TL_CONSTRAINED_DELEGATION_ACL
+                || ptr->tl_data_type == KRB5_TL_LAST_ADMIN_UNLOCK)
                 continue;
             count++;
         }
@@ -1079,7 +1081,8 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
 #endif
                     || ptr->tl_data_type == KRB5_TL_KADM_DATA
                     || ptr->tl_data_type == KDB_TL_USER_INFO
-                    || ptr->tl_data_type == KRB5_TL_CONSTRAINED_DELEGATION_ACL)
+                    || ptr->tl_data_type == KRB5_TL_CONSTRAINED_DELEGATION_ACL
+                    || ptr->tl_data_type == KRB5_TL_LAST_ADMIN_UNLOCK)
                     continue;
                 if ((st = tl_data2berval (ptr, &ber_tl_data[j])) != 0)
                     break;
@@ -1099,6 +1102,22 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
                                               ber_tl_data)) != 0)
                 goto cleanup;
         }
+        if ((st=krb5_dbe_lookup_last_admin_unlock(context, entry,
+                                                  &unlock_time)) != 0)
+            goto cleanup;
+        if (unlock_time != 0) {
+            /* Update last admin unlock */
+            memset(strval, 0, sizeof(strval));
+            if ((strval[0] = getstringtime(unlock_time)) == NULL)
+                goto cleanup;
+
+            if ((st=krb5_add_str_mem_ldap_mod(&mods, "krbLastAdminUnlock",
+                                              LDAP_MOD_REPLACE, strval)) != 0) {
+                free (strval[0]);
+                goto cleanup;
+            }
+            free (strval[0]);
+        }
     }
 
     /* Directory specific attribute */
index be01f394a62d9f99d0806472460be83446265df0..ce697a438ca7012f8f23970a8f8eccd8978d4651 100644 (file)
@@ -54,6 +54,7 @@ static char *kdcrights_subtree[][2] = {
     {"2#subtree#","#krbMaxPwdLife"},
     {"2#subtree#","#krbObjectReferences"},
     {"2#subtree#","#krbLastPwdChange"},
+    {"2#subtree#","#krbLastAdminUnlock"},
     {"6#subtree#","#krbExtraData"},
     {"2#subtree#","#krbPasswordExpiration"},
     {"6#subtree#","#krbLastFailedAuth"},
@@ -82,6 +83,7 @@ static char *adminrights_subtree[][2]={
     {"6#subtree#","#krbPwdMinLength"},
     {"6#subtree#","#krbPwdPolicyReference"},
     {"6#subtree#","#krbLastPwdChange"},
+    {"6#subtree#","#krbLastAdminUnlock"},
     {"6#subtree#","#krbObjectReferences"},
     {"6#subtree#","#krbExtraData"},
     {"6#subtree#","#krbPasswordExpiration"},
@@ -114,6 +116,7 @@ static char *pwdrights_subtree[][2] = {
     {"2#subtree#","#krbPwdMinLength"},
     {"2#subtree#","#krbPwdPolicyReference"},
     {"6#subtree#","#krbLastPwdChange"},
+    {"6#subtree#","#krbLastAdminUnlock"},
     {"2#subtree#","#krbObjectReferences"},
     {"6#subtree#","#krbExtraData"},
     {"6#subtree#","#krbPasswordExpiration"},
@@ -150,6 +153,7 @@ static char *kdcrights_realmcontainer[][2]={
     {"2#subtree#","#krbMaxPwdLife"},
     {"2#subtree#","#krbObjectReferences"},
     {"2#subtree#","#krbLastPwdChange"},
+    {"2#subtree#","#krbLastAdminUnlock"},
     {"6#subtree#","#krbExtraData"},
     {"2#subtree#","#krbPasswordExpiration"},
     {"2#subtree#","#krbDefaultEncSaltTypes"},
@@ -187,6 +191,7 @@ static char *adminrights_realmcontainer[][2]={
     {"6#subtree#","#krbPwdMinLength"},
     {"6#subtree#","#krbPwdPolicyReference"},
     {"6#subtree#","#krbLastPwdChange"},
+    {"6#subtree#","#krbLastAdminUnlock"},
     {"6#subtree#","#krbObjectReferences"},
     {"6#subtree#","#krbExtraData"},
     {"6#subtree#","#krbPasswordExpiration"},
@@ -228,6 +233,7 @@ static char *pwdrights_realmcontainer[][2]={
     {"2#subtree#","#krbPwdMinLength"},
     {"2#subtree#","#krbPwdPolicyReference"},
     {"2#subtree#","#krbLastPwdChange"},
+    {"2#subtree#","#krbLastAdminUnlock"},
     {"2#subtree#","#krbObjectReferences"},
     {"6#subtree#","#krbExtraData"},
     {"6#subtree#","#krbPasswordExpiration"},
index c1a4d7ebe8db1a3c51db1b1f664d8010d5f96804..509c692e67c6edd172878597c391e482822d0b8a 100644 (file)
@@ -96,6 +96,13 @@ locked_check_p(krb5_context context,
                krb5_timestamp lockout_duration,
                krb5_db_entry *entry)
 {
+    krb5_timestamp unlock_time;
+
+    /* If the entry was unlocked since the last failure, it's not locked. */
+    if (krb5_dbe_lookup_last_admin_unlock(context, entry, &unlock_time) == 0 &&
+        entry->last_failed <= unlock_time)
+        return FALSE;
+
     if (max_fail == 0 || entry->fail_auth_count < max_fail)
         return FALSE;
 
@@ -145,6 +152,7 @@ krb5_ldap_lockout_audit(krb5_context context,
     krb5_kvno max_fail = 0;
     krb5_deltat failcnt_interval = 0;
     krb5_deltat lockout_duration = 0;
+    krb5_timestamp unlock_time;
 
     SETUP_CONTEXT();
 
@@ -183,6 +191,13 @@ krb5_ldap_lockout_audit(krb5_context context,
     } else if (!ldap_context->disable_lockout &&
                (status == KRB5KDC_ERR_PREAUTH_FAILED ||
                 status == KRB5KRB_AP_ERR_BAD_INTEGRITY)) {
+        if (krb5_dbe_lookup_last_admin_unlock(context, entry,
+                                              &unlock_time) == 0 &&
+            entry->last_failed <= unlock_time) {
+            /* Reset fail_auth_count after administrative unlock. */
+            entry->fail_auth_count = 0;
+        }
+
         if (failcnt_interval != 0 &&
             stamp > entry->last_failed + failcnt_interval) {
             /* Reset fail_auth_count after failcnt_interval */
index 3d08fbc56eeff93d9aa37ffd2b8c504b1e96ee41..bd33a95067a601f690d5d3afd01adfa3ccdcfe2e 100644 (file)
@@ -23,7 +23,7 @@
 #!/usr/bin/python
 from k5test import *
 
-realm = K5Realm(create_host=False)
+realm = K5Realm(create_host=False, start_kadmind=False)
 
 realm.run_kadminl('addpol -maxfailure 2 -failurecountinterval 5m lockout')
 realm.run_kadminl('modprinc +requires_preauth -policy lockout user')
@@ -44,5 +44,9 @@ if 'Clients credentials have been revoked while getting initial credentials' \
         not in output:
     fail('Expected lockout error message not seen in kinit output')
 
+# Check that modprinc -unlock allows a further attempt.
+output = realm.run_kadminl('modprinc -unlock user')
+realm.kinit(realm.user_princ, password('user'))
+
 success('Account lockout.')