961b92ccc61045df2551805df8bf9bde3897aadd
[ira/wip.git] / source3 / libads / kerberos_verify.c
1 /* 
2    Unix SMB/CIFS implementation.
3    kerberos utility library
4    Copyright (C) Andrew Tridgell 2001
5    Copyright (C) Remus Koos 2001
6    Copyright (C) Luke Howard 2003   
7    Copyright (C) Guenther Deschner 2003
8    Copyright (C) Jim McDonough (jmcd@us.ibm.com) 2003
9    
10    This program is free software; you can redistribute it and/or modify
11    it under the terms of the GNU General Public License as published by
12    the Free Software Foundation; either version 2 of the License, or
13    (at your option) any later version.
14    
15    This program is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU General Public License for more details.
19    
20    You should have received a copy of the GNU General Public License
21    along with this program; if not, write to the Free Software
22    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 */
24
25 #include "includes.h"
26
27 #ifdef HAVE_KRB5
28
29 #if !defined(HAVE_KRB5_PRINC_COMPONENT)
30 const krb5_data *krb5_princ_component(krb5_context, krb5_principal, int );
31 #endif
32
33 /**********************************************************************************
34  Try to verify a ticket using the system keytab... the system keytab has kvno -1 entries, so
35  it's more like what microsoft does... see comment in utils/net_ads.c in the
36  ads_keytab_add_entry function for details.
37 ***********************************************************************************/
38
39 static BOOL ads_keytab_verify_ticket(krb5_context context, krb5_auth_context auth_context,
40                         const DATA_BLOB *ticket, krb5_data *p_packet, krb5_ticket **pp_tkt)
41 {
42         krb5_error_code ret = 0;
43         BOOL auth_ok = False;
44
45         krb5_keytab keytab = NULL;
46         krb5_kt_cursor cursor;
47         krb5_keytab_entry kt_entry;
48         char *princ_name = NULL;
49
50         ZERO_STRUCT(kt_entry);
51         ZERO_STRUCT(cursor);
52
53         ret = krb5_kt_default(context, &keytab);
54         if (ret) {
55                 DEBUG(1, ("ads_keytab_verify_ticket: krb5_kt_default failed (%s)\n", error_message(ret)));
56                 goto out;
57         }
58
59         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
60         if (ret) {
61                 DEBUG(1, ("ads_keytab_verify_ticket: krb5_kt_start_seq_get failed (%s)\n", error_message(ret)));
62                 goto out;
63         }
64
65         while (!krb5_kt_next_entry(context, keytab, &kt_entry, &cursor)) {
66                 ret = krb5_unparse_name(context, kt_entry.principal, &princ_name);
67                 if (ret) {
68                         DEBUG(1, ("ads_keytab_verify_ticket: krb5_unparse_name failed (%s)\n", error_message(ret)));
69                         goto out;
70                 }
71                 /* Look for a CIFS ticket */
72                 if (!StrnCaseCmp(princ_name, "cifs/", 5)) {
73 #ifdef HAVE_KRB5_KEYTAB_ENTRY_KEYBLOCK
74                         krb5_auth_con_setuseruserkey(context, auth_context, &kt_entry.keyblock);
75 #else
76                         krb5_auth_con_setuseruserkey(context, auth_context, &kt_entry.key);
77 #endif
78
79                         p_packet->length = ticket->length;
80                         p_packet->data = (krb5_pointer)ticket->data;
81
82                         if (!(ret = krb5_rd_req(context, &auth_context, p_packet, NULL, NULL, NULL, pp_tkt))) {
83                                 unsigned int keytype;
84                                 krb5_free_unparsed_name(context, princ_name);
85                                 princ_name = NULL;
86 #ifdef HAVE_KRB5_KEYTAB_ENTRY_KEYBLOCK
87                                 keytype = (unsigned int) kt_entry.keyblock.keytype;
88 #else
89                                 keytype = (unsigned int) kt_entry.key.enctype;
90 #endif
91                                 DEBUG(10,("ads_keytab_verify_ticket: enc type [%u] decrypted message !\n",
92                                           keytype));
93                                 auth_ok = True;
94                                 break;
95                         }
96                 }
97                 krb5_free_unparsed_name(context, princ_name);
98                 princ_name = NULL;
99         }
100         if (ret && ret != KRB5_KT_END) {
101                 /* This failed because something went wrong, not because the keytab file was empty. */
102                 DEBUG(1, ("ads_keytab_verify_ticket: krb5_kt_next_entry failed (%s)\n", error_message(ret)));
103                 goto out;
104         }
105
106   out:
107
108         if (princ_name) {
109                 krb5_free_unparsed_name(context, princ_name);
110         }
111         {
112                 krb5_kt_cursor zero_csr;
113                 ZERO_STRUCT(zero_csr);
114                 if ((memcmp(&cursor, &zero_csr, sizeof(krb5_kt_cursor)) != 0) && keytab) {
115                         krb5_kt_end_seq_get(context, keytab, &cursor);
116                 }
117         }
118         if (keytab) {
119                 krb5_kt_close(context, keytab);
120         }
121
122         return auth_ok;
123 }
124
125 /**********************************************************************************
126  Try to verify a ticket using the secrets.tdb.
127 ***********************************************************************************/
128
129 static BOOL ads_secrets_verify_ticket(krb5_context context, krb5_auth_context auth_context,
130                         krb5_principal host_princ,
131                         const DATA_BLOB *ticket, krb5_data *p_packet, krb5_ticket **pp_tkt)
132 {
133         krb5_error_code ret = 0;
134         BOOL auth_ok = False;
135         char *password_s = NULL;
136         krb5_data password;
137         krb5_enctype *enctypes = NULL;
138         int i;
139
140         if (!secrets_init()) {
141                 DEBUG(1,("ads_secrets_verify_ticket: secrets_init failed\n"));
142                 return False;
143         }
144
145         password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
146         if (!password_s) {
147                 DEBUG(1,("ads_secrets_verify_ticket: failed to fetch machine password\n"));
148                 return False;
149         }
150
151         password.data = password_s;
152         password.length = strlen(password_s);
153
154         /* CIFS doesn't use addresses in tickets. This would break NAT. JRA */
155
156         if ((ret = get_kerberos_allowed_etypes(context, &enctypes))) {
157                 DEBUG(1,("ads_secrets_verify_ticket: krb5_get_permitted_enctypes failed (%s)\n", 
158                          error_message(ret)));
159                 goto out;
160         }
161
162         p_packet->length = ticket->length;
163         p_packet->data = (krb5_pointer)ticket->data;
164
165         /* We need to setup a auth context with each possible encoding type in turn. */
166         for (i=0;enctypes[i];i++) {
167                 krb5_keyblock *key = NULL;
168
169                 if (!(key = (krb5_keyblock *)malloc(sizeof(*key)))) {
170                         goto out;
171                 }
172         
173                 if (create_kerberos_key_from_string(context, host_princ, &password, key, enctypes[i])) {
174                         SAFE_FREE(key);
175                         continue;
176                 }
177
178                 krb5_auth_con_setuseruserkey(context, auth_context, key);
179
180                 krb5_free_keyblock(context, key);
181
182                 if (!(ret = krb5_rd_req(context, &auth_context, p_packet, 
183                                         NULL,
184                                         NULL, NULL, pp_tkt))) {
185                         DEBUG(10,("ads_secrets_verify_ticket: enc type [%u] decrypted message !\n",
186                                 (unsigned int)enctypes[i] ));
187                         auth_ok = True;
188                         break;
189                 }
190         
191                 DEBUG((ret != KRB5_BAD_ENCTYPE) ? 3 : 10,
192                                 ("ads_secrets_verify_ticket: enc type [%u] failed to decrypt with error %s\n",
193                                 (unsigned int)enctypes[i], error_message(ret)));
194         }
195
196  out:
197
198         free_kerberos_etypes(context, enctypes);
199         SAFE_FREE(password_s);
200
201         return auth_ok;
202 }
203
204 /**********************************************************************************
205  Verify an incoming ticket and parse out the principal name and 
206  authorization_data if available.
207 ***********************************************************************************/
208
209 NTSTATUS ads_verify_ticket(const char *realm, const DATA_BLOB *ticket, 
210                            char **principal, DATA_BLOB *auth_data,
211                            DATA_BLOB *ap_rep,
212                            DATA_BLOB *session_key)
213 {
214         NTSTATUS sret = NT_STATUS_LOGON_FAILURE;
215         krb5_context context = NULL;
216         krb5_auth_context auth_context = NULL;
217         krb5_data packet;
218         krb5_ticket *tkt = NULL;
219         krb5_rcache rcache = NULL;
220         int ret;
221
222         krb5_principal host_princ = NULL;
223         char *host_princ_s = NULL;
224         BOOL got_replay_mutex = False;
225
226         fstring myname;
227         BOOL auth_ok = False;
228
229         ZERO_STRUCT(packet);
230         ZERO_STRUCTP(auth_data);
231         ZERO_STRUCTP(ap_rep);
232         ZERO_STRUCTP(session_key);
233
234         initialize_krb5_error_table();
235         ret = krb5_init_context(&context);
236         if (ret) {
237                 DEBUG(1,("ads_verify_ticket: krb5_init_context failed (%s)\n", error_message(ret)));
238                 return NT_STATUS_LOGON_FAILURE;
239         }
240
241         ret = krb5_set_default_realm(context, realm);
242         if (ret) {
243                 DEBUG(1,("ads_verify_ticket: krb5_set_default_realm failed (%s)\n", error_message(ret)));
244                 goto out;
245         }
246
247         /* This whole process is far more complex than I would
248            like. We have to go through all this to allow us to store
249            the secret internally, instead of using /etc/krb5.keytab */
250
251         ret = krb5_auth_con_init(context, &auth_context);
252         if (ret) {
253                 DEBUG(1,("ads_verify_ticket: krb5_auth_con_init failed (%s)\n", error_message(ret)));
254                 goto out;
255         }
256
257         name_to_fqdn(myname, global_myname());
258         strlower_m(myname);
259         asprintf(&host_princ_s, "host/%s@%s", myname, lp_realm());
260         ret = krb5_parse_name(context, host_princ_s, &host_princ);
261         if (ret) {
262                 DEBUG(1,("ads_verify_ticket: krb5_parse_name(%s) failed (%s)\n",
263                                         host_princ_s, error_message(ret)));
264                 goto out;
265         }
266
267
268         /* Lock a mutex surrounding the replay as there is no locking in the MIT krb5
269          * code surrounding the replay cache... */
270
271         if (!grab_server_mutex("replay cache mutex")) {
272                 DEBUG(1,("ads_verify_ticket: unable to protect replay cache with mutex.\n"));
273                 goto out;
274         }
275
276         got_replay_mutex = True;
277
278         /*
279          * JRA. We must set the rcache here. This will prevent replay attacks.
280          */
281
282         ret = krb5_get_server_rcache(context, krb5_princ_component(context, host_princ, 0), &rcache);
283         if (ret) {
284                 DEBUG(1,("ads_verify_ticket: krb5_get_server_rcache failed (%s)\n", error_message(ret)));
285                 goto out;
286         }
287
288         ret = krb5_auth_con_setrcache(context, auth_context, rcache);
289         if (ret) {
290                 DEBUG(1,("ads_verify_ticket: krb5_auth_con_setrcache failed (%s)\n", error_message(ret)));
291                 goto out;
292         }
293
294         if (lp_use_kerberos_keytab()) {
295                 auth_ok = ads_keytab_verify_ticket(context, auth_context, ticket, &packet, &tkt);
296         }
297         if (!auth_ok) {
298                 auth_ok = ads_secrets_verify_ticket(context, auth_context, host_princ,
299                                                         ticket, &packet, &tkt);
300         }
301
302         release_server_mutex();
303         got_replay_mutex = False;
304
305         if (!auth_ok) {
306                 DEBUG(3,("ads_verify_ticket: krb5_rd_req with auth failed (%s)\n", 
307                          error_message(ret)));
308                 goto out;
309         }
310
311         ret = krb5_mk_rep(context, auth_context, &packet);
312         if (ret) {
313                 DEBUG(3,("ads_verify_ticket: Failed to generate mutual authentication reply (%s)\n",
314                         error_message(ret)));
315                 goto out;
316         }
317
318         *ap_rep = data_blob(packet.data, packet.length);
319         SAFE_FREE(packet.data);
320         packet.length = 0;
321
322         get_krb5_smb_session_key(context, auth_context, session_key, True);
323         dump_data_pw("SMB session key (from ticket)\n", session_key->data, session_key->length);
324
325 #if 0
326         file_save("/tmp/ticket.dat", ticket->data, ticket->length);
327 #endif
328
329         get_auth_data_from_tkt(auth_data, tkt);
330
331         {
332                 TALLOC_CTX *ctx = talloc_init("pac data");
333                 decode_pac_data(auth_data, ctx);
334                 talloc_destroy(ctx);
335         }
336
337 #if 0
338         if (tkt->enc_part2) {
339                 file_save("/tmp/authdata.dat",
340                           tkt->enc_part2->authorization_data[0]->contents,
341                           tkt->enc_part2->authorization_data[0]->length);
342         }
343 #endif
344
345         if ((ret = krb5_unparse_name(context, get_principal_from_tkt(tkt),
346                                      principal))) {
347                 DEBUG(3,("ads_verify_ticket: krb5_unparse_name failed (%s)\n", 
348                          error_message(ret)));
349                 sret = NT_STATUS_LOGON_FAILURE;
350                 goto out;
351         }
352
353         sret = NT_STATUS_OK;
354
355  out:
356
357         if (got_replay_mutex) {
358                 release_server_mutex();
359         }
360
361         if (!NT_STATUS_IS_OK(sret)) {
362                 data_blob_free(auth_data);
363         }
364
365         if (!NT_STATUS_IS_OK(sret)) {
366                 data_blob_free(ap_rep);
367         }
368
369         if (host_princ) {
370                 krb5_free_principal(context, host_princ);
371         }
372
373         if (tkt != NULL) {
374                 krb5_free_ticket(context, tkt);
375         }
376
377         SAFE_FREE(host_princ_s);
378
379         if (auth_context) {
380                 krb5_auth_con_free(context, auth_context);
381         }
382
383         if (context) {
384                 krb5_free_context(context);
385         }
386
387         return sret;
388 }
389
390 #endif /* HAVE_KRB5 */