Fix bug #6557 - Do not work VFS full_audit
[Samba.git] / source / libads / kerberos_verify.c
blobf48a9f75c9d10202503653dcfd3f78db0b92865f
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, 2005
8 Copyright (C) Jim McDonough (jmcd@us.ibm.com) 2003
9 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005
10 Copyright (C) Jeremy Allison 2007
12 This program is free software; you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation; either version 3 of the License, or
15 (at your option) any later version.
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with this program. If not, see <http://www.gnu.org/licenses/>.
26 #include "includes.h"
27 #include "smb_krb5.h"
29 #ifdef HAVE_KRB5
31 #if !defined(HAVE_KRB5_PRINC_COMPONENT)
32 const krb5_data *krb5_princ_component(krb5_context, krb5_principal, int );
33 #endif
35 /**********************************************************************************
36 Try to verify a ticket using the system keytab... the system keytab has kvno -1 entries, so
37 it's more like what microsoft does... see comment in utils/net_ads.c in the
38 ads_keytab_add_entry function for details.
39 ***********************************************************************************/
41 static bool ads_keytab_verify_ticket(krb5_context context,
42 krb5_auth_context auth_context,
43 const DATA_BLOB *ticket,
44 krb5_ticket **pp_tkt,
45 krb5_keyblock **keyblock,
46 krb5_error_code *perr)
48 krb5_error_code ret = 0;
49 bool auth_ok = False;
50 krb5_keytab keytab = NULL;
51 krb5_kt_cursor kt_cursor;
52 krb5_keytab_entry kt_entry;
53 char *valid_princ_formats[7] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL };
54 char *entry_princ_s = NULL;
55 fstring my_name, my_fqdn;
56 int i;
57 int number_matched_principals = 0;
58 krb5_data packet;
60 *pp_tkt = NULL;
61 *keyblock = NULL;
62 *perr = 0;
64 /* Generate the list of principal names which we expect
65 * clients might want to use for authenticating to the file
66 * service. We allow name$,{host,cifs}/{name,fqdn,name.REALM}. */
68 fstrcpy(my_name, global_myname());
70 my_fqdn[0] = '\0';
71 name_to_fqdn(my_fqdn, global_myname());
73 if (asprintf(&valid_princ_formats[0], "%s$@%s", my_name, lp_realm()) == -1) {
74 goto out;
76 if (asprintf(&valid_princ_formats[1], "host/%s@%s", my_name, lp_realm()) == -1) {
77 goto out;
79 if (asprintf(&valid_princ_formats[2], "host/%s@%s", my_fqdn, lp_realm()) == -1) {
80 goto out;
82 if (asprintf(&valid_princ_formats[3], "host/%s.%s@%s", my_name, lp_realm(), lp_realm()) == -1) {
83 goto out;
85 if (asprintf(&valid_princ_formats[4], "cifs/%s@%s", my_name, lp_realm()) == -1) {
86 goto out;
88 if (asprintf(&valid_princ_formats[5], "cifs/%s@%s", my_fqdn, lp_realm()) == -1) {
89 goto out;
91 if (asprintf(&valid_princ_formats[6], "cifs/%s.%s@%s", my_name, lp_realm(), lp_realm()) == -1) {
92 goto out;
95 ZERO_STRUCT(kt_entry);
96 ZERO_STRUCT(kt_cursor);
98 ret = smb_krb5_open_keytab(context, NULL, False, &keytab);
99 if (ret) {
100 DEBUG(1, ("ads_keytab_verify_ticket: smb_krb5_open_keytab failed (%s)\n", error_message(ret)));
101 goto out;
104 /* Iterate through the keytab. For each key, if the principal
105 * name case-insensitively matches one of the allowed formats,
106 * try verifying the ticket using that principal. */
108 ret = krb5_kt_start_seq_get(context, keytab, &kt_cursor);
109 if (ret) {
110 DEBUG(1, ("ads_keytab_verify_ticket: krb5_kt_start_seq_get failed (%s)\n", error_message(ret)));
111 goto out;
114 while (!auth_ok && (krb5_kt_next_entry(context, keytab, &kt_entry, &kt_cursor) == 0)) {
115 ret = smb_krb5_unparse_name(context, kt_entry.principal, &entry_princ_s);
116 if (ret) {
117 DEBUG(1, ("ads_keytab_verify_ticket: smb_krb5_unparse_name failed (%s)\n",
118 error_message(ret)));
119 goto out;
122 for (i = 0; i < ARRAY_SIZE(valid_princ_formats); i++) {
124 if (!strequal(entry_princ_s, valid_princ_formats[i])) {
125 continue;
128 number_matched_principals++;
129 packet.length = ticket->length;
130 packet.data = (char *)ticket->data;
131 *pp_tkt = NULL;
133 ret = krb5_rd_req_return_keyblock_from_keytab(context, &auth_context, &packet,
134 kt_entry.principal, keytab,
135 NULL, pp_tkt, keyblock);
137 if (ret) {
138 DEBUG(10,("ads_keytab_verify_ticket: "
139 "krb5_rd_req_return_keyblock_from_keytab(%s) failed: %s\n",
140 entry_princ_s, error_message(ret)));
142 /* workaround for MIT:
143 * as krb5_ktfile_get_entry will explicitly
144 * close the krb5_keytab as soon as krb5_rd_req
145 * has successfully decrypted the ticket but the
146 * ticket is not valid yet (due to clockskew)
147 * there is no point in querying more keytab
148 * entries - Guenther */
150 if (ret == KRB5KRB_AP_ERR_TKT_NYV ||
151 ret == KRB5KRB_AP_ERR_TKT_EXPIRED ||
152 ret == KRB5KRB_AP_ERR_SKEW) {
153 break;
155 } else {
156 DEBUG(3,("ads_keytab_verify_ticket: "
157 "krb5_rd_req_return_keyblock_from_keytab succeeded for principal %s\n",
158 entry_princ_s));
159 auth_ok = True;
160 break;
164 /* Free the name we parsed. */
165 SAFE_FREE(entry_princ_s);
167 /* Free the entry we just read. */
168 smb_krb5_kt_free_entry(context, &kt_entry);
169 ZERO_STRUCT(kt_entry);
171 krb5_kt_end_seq_get(context, keytab, &kt_cursor);
173 ZERO_STRUCT(kt_cursor);
175 out:
177 for (i = 0; i < ARRAY_SIZE(valid_princ_formats); i++) {
178 SAFE_FREE(valid_princ_formats[i]);
181 if (!auth_ok) {
182 if (!number_matched_principals) {
183 DEBUG(3, ("ads_keytab_verify_ticket: no keytab principals matched expected file service name.\n"));
184 } else {
185 DEBUG(3, ("ads_keytab_verify_ticket: krb5_rd_req failed for all %d matched keytab principals\n",
186 number_matched_principals));
190 SAFE_FREE(entry_princ_s);
193 krb5_keytab_entry zero_kt_entry;
194 ZERO_STRUCT(zero_kt_entry);
195 if (memcmp(&zero_kt_entry, &kt_entry, sizeof(krb5_keytab_entry))) {
196 smb_krb5_kt_free_entry(context, &kt_entry);
201 krb5_kt_cursor zero_csr;
202 ZERO_STRUCT(zero_csr);
203 if ((memcmp(&kt_cursor, &zero_csr, sizeof(krb5_kt_cursor)) != 0) && keytab) {
204 krb5_kt_end_seq_get(context, keytab, &kt_cursor);
208 if (keytab) {
209 krb5_kt_close(context, keytab);
211 *perr = ret;
212 return auth_ok;
215 /**********************************************************************************
216 Try to verify a ticket using the secrets.tdb.
217 ***********************************************************************************/
219 static krb5_error_code ads_secrets_verify_ticket(krb5_context context,
220 krb5_auth_context auth_context,
221 krb5_principal host_princ,
222 const DATA_BLOB *ticket,
223 krb5_ticket **pp_tkt,
224 krb5_keyblock **keyblock,
225 krb5_error_code *perr)
227 krb5_error_code ret = 0;
228 bool auth_ok = False;
229 char *password_s = NULL;
230 krb5_data password;
231 krb5_enctype enctypes[] = {
232 #if defined(ENCTYPE_ARCFOUR_HMAC)
233 ENCTYPE_ARCFOUR_HMAC,
234 #endif
235 ENCTYPE_DES_CBC_CRC,
236 ENCTYPE_DES_CBC_MD5,
237 ENCTYPE_NULL
239 krb5_data packet;
240 int i;
242 *pp_tkt = NULL;
243 *keyblock = NULL;
244 *perr = 0;
247 if (!secrets_init()) {
248 DEBUG(1,("ads_secrets_verify_ticket: secrets_init failed\n"));
249 *perr = KRB5_CONFIG_CANTOPEN;
250 return False;
253 password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
254 if (!password_s) {
255 DEBUG(1,("ads_secrets_verify_ticket: failed to fetch machine password\n"));
256 *perr = KRB5_LIBOS_CANTREADPWD;
257 return False;
260 password.data = password_s;
261 password.length = strlen(password_s);
263 /* CIFS doesn't use addresses in tickets. This would break NAT. JRA */
265 packet.length = ticket->length;
266 packet.data = (char *)ticket->data;
268 /* We need to setup a auth context with each possible encoding type in turn. */
269 for (i=0;enctypes[i];i++) {
270 krb5_keyblock *key = NULL;
272 if (!(key = SMB_MALLOC_P(krb5_keyblock))) {
273 ret = ENOMEM;
274 goto out;
277 if (create_kerberos_key_from_string(context, host_princ, &password, key, enctypes[i], false)) {
278 SAFE_FREE(key);
279 continue;
282 krb5_auth_con_setuseruserkey(context, auth_context, key);
284 if (!(ret = krb5_rd_req(context, &auth_context, &packet,
285 NULL,
286 NULL, NULL, pp_tkt))) {
287 DEBUG(10,("ads_secrets_verify_ticket: enc type [%u] decrypted message !\n",
288 (unsigned int)enctypes[i] ));
289 auth_ok = True;
290 krb5_copy_keyblock(context, key, keyblock);
291 krb5_free_keyblock(context, key);
292 break;
295 DEBUG((ret != KRB5_BAD_ENCTYPE) ? 3 : 10,
296 ("ads_secrets_verify_ticket: enc type [%u] failed to decrypt with error %s\n",
297 (unsigned int)enctypes[i], error_message(ret)));
299 /* successfully decrypted but ticket is just not valid at the moment */
300 if (ret == KRB5KRB_AP_ERR_TKT_NYV ||
301 ret == KRB5KRB_AP_ERR_TKT_EXPIRED ||
302 ret == KRB5KRB_AP_ERR_SKEW) {
303 krb5_free_keyblock(context, key);
304 break;
307 krb5_free_keyblock(context, key);
311 out:
312 SAFE_FREE(password_s);
313 *perr = ret;
314 return auth_ok;
317 /**********************************************************************************
318 Verify an incoming ticket and parse out the principal name and
319 authorization_data if available.
320 ***********************************************************************************/
322 NTSTATUS ads_verify_ticket(TALLOC_CTX *mem_ctx,
323 const char *realm,
324 time_t time_offset,
325 const DATA_BLOB *ticket,
326 char **principal,
327 struct PAC_DATA **pac_data,
328 DATA_BLOB *ap_rep,
329 DATA_BLOB *session_key,
330 bool use_replay_cache)
332 NTSTATUS sret = NT_STATUS_LOGON_FAILURE;
333 NTSTATUS pac_ret;
334 DATA_BLOB auth_data;
335 krb5_context context = NULL;
336 krb5_auth_context auth_context = NULL;
337 krb5_data packet;
338 krb5_ticket *tkt = NULL;
339 krb5_rcache rcache = NULL;
340 krb5_keyblock *keyblock = NULL;
341 time_t authtime;
342 krb5_error_code ret = 0;
343 int flags = 0;
344 krb5_principal host_princ = NULL;
345 krb5_const_principal client_principal = NULL;
346 char *host_princ_s = NULL;
347 bool auth_ok = False;
348 bool got_auth_data = False;
349 struct named_mutex *mutex = NULL;
351 ZERO_STRUCT(packet);
352 ZERO_STRUCT(auth_data);
354 *principal = NULL;
355 *pac_data = NULL;
356 *ap_rep = data_blob_null;
357 *session_key = data_blob_null;
359 initialize_krb5_error_table();
360 ret = krb5_init_context(&context);
361 if (ret) {
362 DEBUG(1,("ads_verify_ticket: krb5_init_context failed (%s)\n", error_message(ret)));
363 return NT_STATUS_LOGON_FAILURE;
366 if (time_offset != 0) {
367 krb5_set_real_time(context, time(NULL) + time_offset, 0);
370 ret = krb5_set_default_realm(context, realm);
371 if (ret) {
372 DEBUG(1,("ads_verify_ticket: krb5_set_default_realm failed (%s)\n", error_message(ret)));
373 goto out;
376 /* This whole process is far more complex than I would
377 like. We have to go through all this to allow us to store
378 the secret internally, instead of using /etc/krb5.keytab */
380 ret = krb5_auth_con_init(context, &auth_context);
381 if (ret) {
382 DEBUG(1,("ads_verify_ticket: krb5_auth_con_init failed (%s)\n", error_message(ret)));
383 goto out;
386 krb5_auth_con_getflags( context, auth_context, &flags );
387 if ( !use_replay_cache ) {
388 /* Disable default use of a replay cache */
389 flags &= ~KRB5_AUTH_CONTEXT_DO_TIME;
390 krb5_auth_con_setflags( context, auth_context, flags );
393 if (asprintf(&host_princ_s, "%s$", global_myname()) == -1) {
394 goto out;
397 strlower_m(host_princ_s);
398 ret = smb_krb5_parse_name(context, host_princ_s, &host_princ);
399 if (ret) {
400 DEBUG(1,("ads_verify_ticket: smb_krb5_parse_name(%s) failed (%s)\n",
401 host_princ_s, error_message(ret)));
402 goto out;
406 if ( use_replay_cache ) {
408 /* Lock a mutex surrounding the replay as there is no
409 locking in the MIT krb5 code surrounding the replay
410 cache... */
412 mutex = grab_named_mutex(talloc_tos(), "replay cache mutex",
413 10);
414 if (mutex == NULL) {
415 DEBUG(1,("ads_verify_ticket: unable to protect "
416 "replay cache with mutex.\n"));
417 ret = KRB5_CC_IO;
418 goto out;
421 /* JRA. We must set the rcache here. This will prevent
422 replay attacks. */
424 ret = krb5_get_server_rcache(context,
425 krb5_princ_component(context, host_princ, 0),
426 &rcache);
427 if (ret) {
428 DEBUG(1,("ads_verify_ticket: krb5_get_server_rcache "
429 "failed (%s)\n", error_message(ret)));
430 goto out;
433 ret = krb5_auth_con_setrcache(context, auth_context, rcache);
434 if (ret) {
435 DEBUG(1,("ads_verify_ticket: krb5_auth_con_setrcache "
436 "failed (%s)\n", error_message(ret)));
437 goto out;
441 /* Try secrets.tdb first and fallback to the krb5.keytab if
442 necessary */
444 auth_ok = ads_secrets_verify_ticket(context, auth_context, host_princ,
445 ticket, &tkt, &keyblock, &ret);
447 if (!auth_ok &&
448 (ret == KRB5KRB_AP_ERR_TKT_NYV ||
449 ret == KRB5KRB_AP_ERR_TKT_EXPIRED ||
450 ret == KRB5KRB_AP_ERR_SKEW)) {
451 goto auth_failed;
454 if (!auth_ok && lp_use_kerberos_keytab()) {
455 auth_ok = ads_keytab_verify_ticket(context, auth_context,
456 ticket, &tkt, &keyblock, &ret);
459 if ( use_replay_cache ) {
460 TALLOC_FREE(mutex);
461 #if 0
462 /* Heimdal leaks here, if we fix the leak, MIT crashes */
463 if (rcache) {
464 krb5_rc_close(context, rcache);
466 #endif
469 auth_failed:
470 if (!auth_ok) {
471 DEBUG(3,("ads_verify_ticket: krb5_rd_req with auth failed (%s)\n",
472 error_message(ret)));
473 /* Try map the error return in case it's something like
474 * a clock skew error.
476 sret = krb5_to_nt_status(ret);
477 if (NT_STATUS_IS_OK(sret) || NT_STATUS_EQUAL(sret,NT_STATUS_UNSUCCESSFUL)) {
478 sret = NT_STATUS_LOGON_FAILURE;
480 DEBUG(10,("ads_verify_ticket: returning error %s\n",
481 nt_errstr(sret) ));
482 goto out;
485 authtime = get_authtime_from_tkt(tkt);
486 client_principal = get_principal_from_tkt(tkt);
488 ret = krb5_mk_rep(context, auth_context, &packet);
489 if (ret) {
490 DEBUG(3,("ads_verify_ticket: Failed to generate mutual authentication reply (%s)\n",
491 error_message(ret)));
492 goto out;
495 *ap_rep = data_blob(packet.data, packet.length);
496 if (packet.data) {
497 kerberos_free_data_contents(context, &packet);
498 ZERO_STRUCT(packet);
501 get_krb5_smb_session_key(context, auth_context, session_key, True);
502 dump_data_pw("SMB session key (from ticket)\n", session_key->data, session_key->length);
504 #if 0
505 file_save("/tmp/ticket.dat", ticket->data, ticket->length);
506 #endif
508 /* continue when no PAC is retrieved or we couldn't decode the PAC
509 (like accounts that have the UF_NO_AUTH_DATA_REQUIRED flag set, or
510 Kerberos tickets encrypted using a DES key) - Guenther */
512 got_auth_data = get_auth_data_from_tkt(mem_ctx, &auth_data, tkt);
513 if (!got_auth_data) {
514 DEBUG(3,("ads_verify_ticket: did not retrieve auth data. continuing without PAC\n"));
517 if (got_auth_data) {
518 pac_ret = decode_pac_data(mem_ctx, &auth_data, context, keyblock, client_principal, authtime, pac_data);
519 if (!NT_STATUS_IS_OK(pac_ret)) {
520 DEBUG(3,("ads_verify_ticket: failed to decode PAC_DATA: %s\n", nt_errstr(pac_ret)));
521 *pac_data = NULL;
523 data_blob_free(&auth_data);
526 #if 0
527 #if defined(HAVE_KRB5_TKT_ENC_PART2)
528 /* MIT */
529 if (tkt->enc_part2) {
530 file_save("/tmp/authdata.dat",
531 tkt->enc_part2->authorization_data[0]->contents,
532 tkt->enc_part2->authorization_data[0]->length);
534 #else
535 /* Heimdal */
536 if (tkt->ticket.authorization_data) {
537 file_save("/tmp/authdata.dat",
538 tkt->ticket.authorization_data->val->ad_data.data,
539 tkt->ticket.authorization_data->val->ad_data.length);
541 #endif
542 #endif
544 if ((ret = smb_krb5_unparse_name(context, client_principal, principal))) {
545 DEBUG(3,("ads_verify_ticket: smb_krb5_unparse_name failed (%s)\n",
546 error_message(ret)));
547 sret = NT_STATUS_LOGON_FAILURE;
548 goto out;
551 sret = NT_STATUS_OK;
553 out:
555 TALLOC_FREE(mutex);
557 if (!NT_STATUS_IS_OK(sret)) {
558 data_blob_free(&auth_data);
561 if (!NT_STATUS_IS_OK(sret)) {
562 data_blob_free(ap_rep);
565 if (host_princ) {
566 krb5_free_principal(context, host_princ);
569 if (keyblock) {
570 krb5_free_keyblock(context, keyblock);
573 if (tkt != NULL) {
574 krb5_free_ticket(context, tkt);
577 SAFE_FREE(host_princ_s);
579 if (auth_context) {
580 krb5_auth_con_free(context, auth_context);
583 if (context) {
584 krb5_free_context(context);
587 return sret;
590 #endif /* HAVE_KRB5 */