1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 /* plugins/kdb/db2/lockout.c */ 3 /* 4 * Copyright (C) 2009 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 <stdio.h> 30 #include <errno.h> 31 #include <kadm5/server_internal.h> 32 #include "kdb5.h" 33 #include "kdb_db2.h" 34 35 /* 36 * Helper routines for databases that wish to use the default 37 * principal lockout functionality. 38 */ 39 40 static krb5_error_code 41 lookup_lockout_policy(krb5_context context, 42 krb5_db_entry *entry, 43 krb5_kvno *pw_max_fail, 44 krb5_deltat *pw_failcnt_interval, 45 krb5_deltat *pw_lockout_duration) 46 { 47 krb5_tl_data tl_data; 48 krb5_error_code code; 49 osa_princ_ent_rec adb; 50 XDR xdrs; 51 52 *pw_max_fail = 0; 53 *pw_failcnt_interval = 0; 54 *pw_lockout_duration = 0; 55 56 tl_data.tl_data_type = KRB5_TL_KADM_DATA; 57 58 code = krb5_dbe_lookup_tl_data(context, entry, &tl_data); 59 if (code != 0 || tl_data.tl_data_length == 0) 60 return code; 61 62 memset(&adb, 0, sizeof(adb)); 63 xdrmem_create(&xdrs, (char *)tl_data.tl_data_contents, 64 tl_data.tl_data_length, XDR_DECODE); 65 if (!xdr_osa_princ_ent_rec(&xdrs, &adb)) { 66 xdr_destroy(&xdrs); 67 return KADM5_XDR_FAILURE; 68 } 69 70 if (adb.policy != NULL) { 71 osa_policy_ent_t policy = NULL; 72 73 code = krb5_db2_get_policy(context, adb.policy, &policy); 74 if (code == 0) { 75 *pw_max_fail = policy->pw_max_fail; 76 *pw_failcnt_interval = policy->pw_failcnt_interval; 77 *pw_lockout_duration = policy->pw_lockout_duration; 78 krb5_db_free_policy(context, policy); 79 } 80 } 81 82 xdr_destroy(&xdrs); 83 84 xdrmem_create(&xdrs, NULL, 0, XDR_FREE); 85 xdr_osa_princ_ent_rec(&xdrs, &adb); 86 xdr_destroy(&xdrs); 87 88 return 0; 89 } 90 91 /* draft-behera-ldap-password-policy-10.txt 7.1 */ 92 static krb5_boolean 93 locked_check_p(krb5_context context, 94 krb5_timestamp stamp, 95 krb5_kvno max_fail, 96 krb5_timestamp lockout_duration, 97 krb5_db_entry *entry) 98 { 99 krb5_timestamp unlock_time; 100 101 /* If the entry was unlocked since the last failure, it's not locked. */ 102 if (krb5_dbe_lookup_last_admin_unlock(context, entry, &unlock_time) == 0 && 103 !ts_after(entry->last_failed, unlock_time)) 104 return FALSE; 105 106 if (max_fail == 0 || entry->fail_auth_count < max_fail) 107 return FALSE; 108 109 if (lockout_duration == 0) 110 return TRUE; /* principal permanently locked */ 111 112 return ts_after(ts_incr(entry->last_failed, lockout_duration), stamp); 113 } 114 115 krb5_error_code 116 krb5_db2_lockout_check_policy(krb5_context context, 117 krb5_db_entry *entry, 118 krb5_timestamp stamp) 119 { 120 krb5_error_code code; 121 krb5_kvno max_fail = 0; 122 krb5_deltat failcnt_interval = 0; 123 krb5_deltat lockout_duration = 0; 124 krb5_db2_context *db_ctx = context->dal_handle->db_context; 125 126 if (db_ctx->disable_lockout) 127 return 0; 128 129 code = lookup_lockout_policy(context, entry, &max_fail, 130 &failcnt_interval, 131 &lockout_duration); 132 if (code != 0) 133 return code; 134 135 if (locked_check_p(context, stamp, max_fail, lockout_duration, entry)) 136 return KRB5KDC_ERR_CLIENT_REVOKED; 137 138 return 0; 139 } 140 141 krb5_error_code 142 krb5_db2_lockout_audit(krb5_context context, 143 krb5_db_entry *entry, 144 krb5_timestamp stamp, 145 krb5_error_code status) 146 { 147 krb5_error_code code; 148 krb5_kvno max_fail = 0; 149 krb5_deltat failcnt_interval = 0; 150 krb5_deltat lockout_duration = 0; 151 krb5_db2_context *db_ctx = context->dal_handle->db_context; 152 krb5_boolean need_update = FALSE; 153 krb5_timestamp unlock_time; 154 155 switch (status) { 156 case 0: 157 case KRB5KDC_ERR_PREAUTH_FAILED: 158 case KRB5KRB_AP_ERR_BAD_INTEGRITY: 159 break; 160 default: 161 return 0; 162 } 163 164 if (entry == NULL) 165 return 0; 166 167 if (!db_ctx->disable_lockout) { 168 code = lookup_lockout_policy(context, entry, &max_fail, 169 &failcnt_interval, &lockout_duration); 170 if (code != 0) 171 return code; 172 } 173 174 /* 175 * Don't continue to modify the DB for an already locked account. 176 * (In most cases, status will be KRB5KDC_ERR_CLIENT_REVOKED, and 177 * this check is unneeded, but in rare cases, we can fail with an 178 * integrity error or preauth failure before a policy check.) 179 */ 180 if (locked_check_p(context, stamp, max_fail, lockout_duration, entry)) 181 return 0; 182 183 /* Only mark the authentication as successful if the entry 184 * required preauthentication, otherwise we have no idea. */ 185 if (status == 0 && (entry->attributes & KRB5_KDB_REQUIRES_PRE_AUTH)) { 186 if (!db_ctx->disable_lockout && entry->fail_auth_count != 0) { 187 entry->fail_auth_count = 0; 188 need_update = TRUE; 189 } 190 if (!db_ctx->disable_last_success) { 191 entry->last_success = stamp; 192 need_update = TRUE; 193 } 194 } else if (!db_ctx->disable_lockout && 195 (status == KRB5KDC_ERR_PREAUTH_FAILED || 196 status == KRB5KRB_AP_ERR_BAD_INTEGRITY)) { 197 if (krb5_dbe_lookup_last_admin_unlock(context, entry, 198 &unlock_time) == 0 && 199 !ts_after(entry->last_failed, unlock_time)) { 200 /* Reset fail_auth_count after administrative unlock. */ 201 entry->fail_auth_count = 0; 202 } 203 204 if (failcnt_interval != 0 && 205 ts_after(stamp, ts_incr(entry->last_failed, failcnt_interval))) { 206 /* Reset fail_auth_count after failcnt_interval. */ 207 entry->fail_auth_count = 0; 208 } 209 210 entry->last_failed = stamp; 211 entry->fail_auth_count++; 212 need_update = TRUE; 213 } 214 215 if (need_update) { 216 code = krb5_db2_put_principal(context, entry, NULL); 217 if (code != 0) 218 return code; 219 } 220 221 return 0; 222 } 223