From: Greg Hudson Date: Tue, 5 Oct 2010 14:53:09 +0000 (+0000) Subject: Propagate modprinc -unlock from master to slave KDCs X-Git-Tag: krb5-1.9-beta1~39 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=96f2a016991c199be477b6abd48824ec1cb6641f;p=krb5.git Propagate modprinc -unlock from master to slave KDCs 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 --- diff --git a/src/include/kdb.h b/src/include/kdb.h index dac44ea55..456227917 100644 --- a/src/include/kdb.h +++ b/src/include/kdb.h @@ -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 /* IssuerDNSubjectDN */ +#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, diff --git a/src/kadmin/cli/kadmin.c b/src/kadmin/cli/kadmin.c index 9e5bc4479..ff6eeca6b 100644 --- a/src/kadmin/cli/kadmin.c +++ b/src/kadmin/cli/kadmin.c @@ -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])) { diff --git a/src/lib/kdb/kdb5.c b/src/lib/kdb/kdb5.c index 5e00d127e..b37a5c110 100644 --- a/src/lib/kdb/kdb5.c +++ b/src/lib/kdb/kdb5.c @@ -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) diff --git a/src/lib/kdb/libkdb5.exports b/src/lib/kdb/libkdb5.exports index f3f6a8021..f9f66eba2 100644 --- a/src/lib/kdb/libkdb5.exports +++ b/src/lib/kdb/libkdb5.exports @@ -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 diff --git a/src/plugins/kdb/db2/lockout.c b/src/plugins/kdb/db2/lockout.c index 3ba53d214..b47361124 100644 --- a/src/plugins/kdb/db2/lockout.c +++ b/src/plugins/kdb/db2/lockout.c @@ -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. */ diff --git a/src/plugins/kdb/ldap/libkdb_ldap/kerberos.ldif b/src/plugins/kdb/ldap/libkdb_ldap/kerberos.ldif index 886f8b435..33a236564 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/kerberos.ldif +++ b/src/plugins/kdb/ldap/libkdb_ldap/kerberos.ldif @@ -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. diff --git a/src/plugins/kdb/ldap/libkdb_ldap/kerberos.schema b/src/plugins/kdb/ldap/libkdb_ldap/kerberos.schema index 65e07d6cd..f4f8aca3f 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/kerberos.schema +++ b/src/plugins/kdb/ldap/libkdb_ldap/kerberos.schema @@ -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. diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c index e9d50196d..86fa4d1e5 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c +++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c @@ -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; diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c index 68b08c512..0805019db 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c +++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c @@ -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 diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h index 3942e3f42..7a7a8067f 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h +++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h @@ -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() diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c index 27a3a6441..140db1a47 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c +++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c @@ -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 */ diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_service_rights.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_service_rights.c index be01f394a..ce697a438 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_service_rights.c +++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_service_rights.c @@ -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"}, diff --git a/src/plugins/kdb/ldap/libkdb_ldap/lockout.c b/src/plugins/kdb/ldap/libkdb_ldap/lockout.c index c1a4d7ebe..509c692e6 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/lockout.c +++ b/src/plugins/kdb/ldap/libkdb_ldap/lockout.c @@ -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 */ diff --git a/src/tests/t_lockout.py b/src/tests/t_lockout.py index 3d08fbc56..bd33a9506 100644 --- a/src/tests/t_lockout.py +++ b/src/tests/t_lockout.py @@ -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.')