SA-2011-006 KDC denial of service [CVE-2011-1527 CVE-2011-1528 CVE-2011-1529]
[krb5.git] / src / plugins / kdb / ldap / libkdb_ldap / lockout.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  * plugins/kdb/ldap/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 <stdio.h>
32 #include <errno.h>
33
34 #include <k5-int.h>
35 #include <kadm5/admin.h>
36 #include <kdb.h>
37
38 #include "kdb_ldap.h"
39 #include "princ_xdr.h"
40 #include "ldap_principal.h"
41 #include "ldap_pwd_policy.h"
42 #include "ldap_tkt_policy.h"
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
68     code = krb5_lookup_tl_kadm_data(&tl_data, &adb);
69     if (code != 0)
70         return code;
71
72     if (adb.policy != NULL) {
73         osa_policy_ent_t policy = NULL;
74
75         code = krb5_ldap_get_password_policy(context, adb.policy, &policy);
76         if (code == 0) {
77             *pw_max_fail = policy->pw_max_fail;
78             *pw_failcnt_interval = policy->pw_failcnt_interval;
79             *pw_lockout_duration = policy->pw_lockout_duration;
80         }
81         krb5_ldap_free_password_policy(context, policy);
82     }
83
84     xdrmem_create(&xdrs, NULL, 0, XDR_FREE);
85     ldap_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         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 (stamp < entry->last_failed + lockout_duration);
113 }
114
115 krb5_error_code
116 krb5_ldap_lockout_check_policy(krb5_context context,
117                                krb5_db_entry *entry,
118                                krb5_timestamp stamp)
119 {
120     krb5_error_code code;
121     kdb5_dal_handle *dal_handle;
122     krb5_ldap_context *ldap_context;
123     krb5_kvno max_fail = 0;
124     krb5_deltat failcnt_interval = 0;
125     krb5_deltat lockout_duration = 0;
126
127     SETUP_CONTEXT();
128     if (ldap_context->disable_lockout)
129         return 0;
130
131     code = lookup_lockout_policy(context, entry, &max_fail,
132                                  &failcnt_interval,
133                                  &lockout_duration);
134     if (code != 0 || failcnt_interval == 0)
135         return code;
136
137     if (locked_check_p(context, stamp, max_fail, lockout_duration, entry))
138         return KRB5KDC_ERR_CLIENT_REVOKED;
139
140     return 0;
141 }
142
143 krb5_error_code
144 krb5_ldap_lockout_audit(krb5_context context,
145                         krb5_db_entry *entry,
146                         krb5_timestamp stamp,
147                         krb5_error_code status)
148 {
149     krb5_error_code code;
150     kdb5_dal_handle *dal_handle;
151     krb5_ldap_context *ldap_context;
152     krb5_kvno max_fail = 0;
153     krb5_deltat failcnt_interval = 0;
154     krb5_deltat lockout_duration = 0;
155     krb5_timestamp unlock_time;
156
157     SETUP_CONTEXT();
158
159     switch (status) {
160     case 0:
161     case KRB5KDC_ERR_PREAUTH_FAILED:
162     case KRB5KRB_AP_ERR_BAD_INTEGRITY:
163         break;
164     default:
165         return 0;
166     }
167
168     if (entry == NULL)
169         return 0;
170
171     if (!ldap_context->disable_lockout) {
172         code = lookup_lockout_policy(context, entry, &max_fail,
173                                      &failcnt_interval,
174                                      &lockout_duration);
175         if (code != 0)
176             return code;
177     }
178
179     /*
180      * Don't continue to modify the DB for an already locked account.
181      * (In most cases, status will be KRB5KDC_ERR_CLIENT_REVOKED, and
182      * this check is unneeded, but in rare cases, we can fail with an
183      * integrity error or preauth failure before a policy check.)
184      */
185     if (locked_check_p(context, stamp, max_fail, lockout_duration, entry))
186         return 0;
187
188     entry->mask = 0;
189
190     /* Only mark the authentication as successful if the entry
191      * required preauthentication, otherwise we have no idea. */
192     if (status == 0 && (entry->attributes & KRB5_KDB_REQUIRES_PRE_AUTH)) {
193         if (!ldap_context->disable_lockout && entry->fail_auth_count != 0) {
194             entry->fail_auth_count = 0;
195             entry->mask |= KADM5_FAIL_AUTH_COUNT;
196         }
197         if (!ldap_context->disable_last_success) {
198             entry->last_success = stamp;
199             entry->mask |= KADM5_LAST_SUCCESS;
200         }
201     } else if (!ldap_context->disable_lockout &&
202                (status == KRB5KDC_ERR_PREAUTH_FAILED ||
203                 status == KRB5KRB_AP_ERR_BAD_INTEGRITY)) {
204         if (krb5_dbe_lookup_last_admin_unlock(context, entry,
205                                               &unlock_time) == 0 &&
206             entry->last_failed <= unlock_time) {
207             /* Reset fail_auth_count after administrative unlock. */
208             entry->fail_auth_count = 0;
209             entry->mask |= KADM5_FAIL_AUTH_COUNT;
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             entry->mask |= KADM5_FAIL_AUTH_COUNT;
217         }
218
219         entry->last_failed = stamp;
220         entry->mask |= KADM5_LAST_FAILED | KADM5_FAIL_AUTH_COUNT_INCREMENT;
221     }
222
223     if (entry->mask) {
224         code = krb5_ldap_put_principal(context, entry, NULL);
225         if (code != 0)
226             return code;
227     }
228
229     return 0;
230 }