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