1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 /* plugins/kdb/lmdb/lockout.c */ 3 /* 4 * Copyright (C) 2009, 2018 by the Massachusetts Institute of Technology. 5 * All rights reserved. 6 * 7 * Export of this software from the United States of America may 8 * require a specific license from the United States Government. 9 * It is the responsibility of any person or organization contemplating 10 * export to obtain such a license before exporting. 11 * 12 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and 13 * distribute this software and its documentation for any purpose and 14 * without fee is hereby granted, provided that the above copyright 15 * notice appear in all copies and that both that copyright notice and 16 * this permission notice appear in supporting documentation, and that 17 * the name of M.I.T. not be used in advertising or publicity pertaining 18 * to distribution of the software without specific, written prior 19 * permission. Furthermore if you modify this software you must label 20 * your software as modified software and not distribute it in such a 21 * fashion that it might be confused with the original M.I.T. software. 22 * M.I.T. makes no representations about the suitability of 23 * this software for any purpose. It is provided "as is" without express 24 * or implied warranty. 25 */ 26 27 #include "k5-int.h" 28 #include "kdb.h" 29 #include <kadm5/server_internal.h> 30 #include "kdb5.h" 31 #include "klmdb-int.h" 32 33 static krb5_error_code 34 lookup_lockout_policy(krb5_context context, krb5_db_entry *entry, 35 krb5_kvno *pw_max_fail, krb5_deltat *pw_failcnt_interval, 36 krb5_deltat *pw_lockout_duration) 37 { 38 krb5_tl_data tl_data; 39 krb5_error_code code; 40 osa_princ_ent_rec adb; 41 XDR xdrs; 42 43 *pw_max_fail = 0; 44 *pw_failcnt_interval = 0; 45 *pw_lockout_duration = 0; 46 47 tl_data.tl_data_type = KRB5_TL_KADM_DATA; 48 49 code = krb5_dbe_lookup_tl_data(context, entry, &tl_data); 50 if (code != 0 || tl_data.tl_data_length == 0) 51 return code; 52 53 memset(&adb, 0, sizeof(adb)); 54 xdrmem_create(&xdrs, (char *)tl_data.tl_data_contents, 55 tl_data.tl_data_length, XDR_DECODE); 56 if (!xdr_osa_princ_ent_rec(&xdrs, &adb)) { 57 xdr_destroy(&xdrs); 58 return KADM5_XDR_FAILURE; 59 } 60 61 if (adb.policy != NULL) { 62 osa_policy_ent_t policy = NULL; 63 64 code = klmdb_get_policy(context, adb.policy, &policy); 65 if (code == 0) { 66 *pw_max_fail = policy->pw_max_fail; 67 *pw_failcnt_interval = policy->pw_failcnt_interval; 68 *pw_lockout_duration = policy->pw_lockout_duration; 69 krb5_db_free_policy(context, policy); 70 } 71 } 72 73 xdr_destroy(&xdrs); 74 75 xdrmem_create(&xdrs, NULL, 0, XDR_FREE); 76 xdr_osa_princ_ent_rec(&xdrs, &adb); 77 xdr_destroy(&xdrs); 78 79 return 0; 80 } 81 82 /* draft-behera-ldap-password-policy-10.txt 7.1 */ 83 static krb5_boolean 84 locked_check_p(krb5_context context, krb5_timestamp stamp, krb5_kvno max_fail, 85 krb5_timestamp lockout_duration, krb5_db_entry *entry) 86 { 87 krb5_timestamp unlock_time; 88 89 /* If the entry was unlocked since the last failure, it's not locked. */ 90 if (krb5_dbe_lookup_last_admin_unlock(context, entry, &unlock_time) == 0 && 91 !ts_after(entry->last_failed, unlock_time)) 92 return FALSE; 93 94 if (max_fail == 0 || entry->fail_auth_count < max_fail) 95 return FALSE; 96 97 if (lockout_duration == 0) 98 return TRUE; /* principal permanently locked */ 99 100 return ts_after(ts_incr(entry->last_failed, lockout_duration), stamp); 101 } 102 103 krb5_error_code 104 klmdb_lockout_check_policy(krb5_context context, krb5_db_entry *entry, 105 krb5_timestamp stamp) 106 { 107 krb5_error_code code; 108 krb5_kvno max_fail = 0; 109 krb5_deltat failcnt_interval = 0; 110 krb5_deltat lockout_duration = 0; 111 112 code = lookup_lockout_policy(context, entry, &max_fail, &failcnt_interval, 113 &lockout_duration); 114 if (code != 0) 115 return code; 116 117 if (locked_check_p(context, stamp, max_fail, lockout_duration, entry)) 118 return KRB5KDC_ERR_CLIENT_REVOKED; 119 120 return 0; 121 } 122 123 krb5_error_code 124 klmdb_lockout_audit(krb5_context context, krb5_db_entry *entry, 125 krb5_timestamp stamp, krb5_error_code status, 126 krb5_boolean disable_last_success, 127 krb5_boolean disable_lockout) 128 { 129 krb5_error_code ret; 130 krb5_kvno max_fail = 0; 131 krb5_deltat failcnt_interval = 0, lockout_duration = 0; 132 krb5_boolean zero_fail_count = FALSE; 133 krb5_boolean set_last_success = FALSE, set_last_failure = FALSE; 134 krb5_timestamp unlock_time; 135 136 if (status != 0 && status != KRB5KDC_ERR_PREAUTH_FAILED && 137 status != KRB5KRB_AP_ERR_BAD_INTEGRITY) 138 return 0; 139 140 if (!disable_lockout) { 141 ret = lookup_lockout_policy(context, entry, &max_fail, 142 &failcnt_interval, &lockout_duration); 143 if (ret) 144 return ret; 145 } 146 147 /* 148 * Don't continue to modify the DB for an already locked account. 149 * (In most cases, status will be KRB5KDC_ERR_CLIENT_REVOKED, and 150 * this check is unneeded, but in rare cases, we can fail with an 151 * integrity error or preauth failure before a policy check.) 152 */ 153 if (locked_check_p(context, stamp, max_fail, lockout_duration, entry)) 154 return 0; 155 156 /* Only mark the authentication as successful if the entry 157 * required preauthentication; otherwise we have no idea. */ 158 if (status == 0 && (entry->attributes & KRB5_KDB_REQUIRES_PRE_AUTH)) { 159 if (!disable_lockout && entry->fail_auth_count != 0) 160 zero_fail_count = TRUE; 161 if (!disable_last_success) 162 set_last_success = TRUE; 163 } else if (status != 0 && !disable_lockout) { 164 /* Reset the failure counter after an administrative unlock. */ 165 if (krb5_dbe_lookup_last_admin_unlock(context, entry, 166 &unlock_time) == 0 && 167 !ts_after(entry->last_failed, unlock_time)) 168 zero_fail_count = TRUE; 169 170 /* Reset the failure counter after failcnt_interval. */ 171 if (failcnt_interval != 0 && 172 ts_after(stamp, ts_incr(entry->last_failed, failcnt_interval))) 173 zero_fail_count = TRUE; 174 175 set_last_failure = TRUE; 176 } 177 178 return klmdb_update_lockout(context, entry, stamp, zero_fail_count, 179 set_last_success, set_last_failure); 180 } 181