libsecurity: Simplify struct ace_condition_script
[Samba.git] / third_party / heimdal / lib / gssapi / krb5 / acquire_cred.c
blob211dcaa7f7532b467e6b76a3c54673e2cad806b8
1 /*
2 * Copyright (c) 1997 - 2005 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the Institute nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
34 #include "gsskrb5_locl.h"
37 * Find an element in a cred store. Returns GSS_S_COMPLETE if the cred store
38 * is absent or well formed, irrespective of whether the element exists. The
39 * caller should check for *value != NULL before using; values are typically
40 * optional, hence this behavior. (The caller should validate the return
41 * value at least once though, to check it is well-formed.)
43 OM_uint32
44 __gsskrb5_cred_store_find(OM_uint32 *minor_status,
45 gss_const_key_value_set_t cred_store,
46 const char *key,
47 const char **value)
49 size_t i;
51 *value = NULL;
53 if (cred_store == GSS_C_NO_CRED_STORE)
54 return GSS_S_COMPLETE;
55 else if (cred_store->count == 0) {
56 *minor_status = GSS_KRB5_S_G_BAD_USAGE;
57 return GSS_S_NO_CRED;
60 for (i = 0; i < cred_store->count; i++) {
61 if (strcmp(key, cred_store->elements[i].key) == 0) {
62 if (*value) {
63 *value = NULL;
64 *minor_status = GSS_KRB5_S_G_BAD_USAGE;
65 return GSS_S_DUPLICATE_ELEMENT;
67 *value = cred_store->elements[i].value;
71 return GSS_S_COMPLETE;
74 OM_uint32
75 __gsskrb5_ccache_lifetime(OM_uint32 *minor_status,
76 krb5_context context,
77 krb5_ccache id,
78 krb5_principal principal,
79 OM_uint32 *lifetime)
81 krb5_error_code kret;
82 time_t left;
84 kret = krb5_cc_get_lifetime(context, id, &left);
85 if (kret) {
86 *minor_status = kret;
87 return GSS_S_FAILURE;
90 *lifetime = left;
92 return GSS_S_COMPLETE;
98 static krb5_error_code
99 get_system_keytab(krb5_context context,
100 gss_const_key_value_set_t cred_store,
101 krb5_keytab *keytab)
103 krb5_error_code kret;
104 const char *cs_ktname;
105 OM_uint32 tmp;
107 __gsskrb5_cred_store_find(&tmp, cred_store, "keytab", &cs_ktname);
109 HEIMDAL_MUTEX_lock(&gssapi_keytab_mutex);
111 if (cs_ktname)
112 kret = krb5_kt_resolve(context, cs_ktname, keytab);
113 else if (_gsskrb5_keytab != NULL) {
114 char *name = NULL;
116 kret = krb5_kt_get_full_name(context, _gsskrb5_keytab, &name);
117 if (kret == 0) {
118 kret = krb5_kt_resolve(context, name, keytab);
119 krb5_xfree(name);
121 } else
122 kret = krb5_kt_default(context, keytab);
124 HEIMDAL_MUTEX_unlock(&gssapi_keytab_mutex);
126 return (kret);
129 static krb5_error_code
130 get_client_keytab(krb5_context context,
131 gss_const_key_value_set_t cred_store,
132 krb5_const_principal principal,
133 krb5_keytab *keytab)
135 krb5_error_code ret;
136 const char *cs_ktname;
137 OM_uint32 tmp;
139 __gsskrb5_cred_store_find(&tmp, cred_store, "client_keytab", &cs_ktname);
141 if (cs_ktname)
142 ret = krb5_kt_resolve(context, cs_ktname, keytab);
143 else {
144 char *name = NULL;
145 ret = _krb5_kt_client_default_name(context, &name);
146 if (ret == 0)
147 ret = krb5_kt_resolve(context, name, keytab);
148 krb5_xfree(name);
151 if (ret == 0 && principal) {
152 krb5_keytab_entry entry;
154 ret = krb5_kt_get_entry(context, *keytab, principal,
155 0, 0, &entry);
156 if (ret == 0)
157 krb5_kt_free_entry(context, &entry);
160 if (ret) {
161 if (*keytab) {
162 krb5_kt_close(context, *keytab);
163 *keytab = NULL;
166 ret = get_system_keytab(context, GSS_C_NO_CRED_STORE, keytab);
169 return ret;
172 static krb5_boolean
173 is_valid_password_cred_store(gss_const_key_value_set_t cred_store)
175 size_t i;
177 if (cred_store == GSS_C_NO_CRED_STORE)
178 return TRUE;
180 /* XXX don't check keytab, someday we will allow password+acceptor creds */
181 for (i = 0; i < cred_store->count; i++) {
182 if (strcmp(cred_store->elements[i].key, "ccache") == 0 ||
183 strcmp(cred_store->elements[i].key, "client_keytab") == 0)
184 return FALSE;
187 return TRUE;
191 * This function produces a cred with a MEMORY ccache containing a TGT
192 * acquired with a password.
194 static OM_uint32
195 acquire_cred_with_password(OM_uint32 *minor_status,
196 krb5_context context,
197 const char *password,
198 OM_uint32 time_req,
199 gss_OID_set desired_mechs,
200 gss_cred_usage_t cred_usage,
201 gss_const_key_value_set_t cred_store,
202 gsskrb5_cred handle)
204 OM_uint32 ret = GSS_S_FAILURE;
205 krb5_creds cred;
206 krb5_init_creds_context ctx = NULL;
207 krb5_get_init_creds_opt *opt = NULL;
208 krb5_ccache ccache = NULL;
209 krb5_error_code kret;
210 time_t now;
211 OM_uint32 left;
212 const char *realm;
214 if (!is_valid_password_cred_store(cred_store)) {
215 *minor_status = GSS_KRB5_S_G_BAD_PASSWORD_CRED_STORE;
216 return GSS_S_NO_CRED;
219 if (cred_usage == GSS_C_ACCEPT) {
221 * TODO: Here we should eventually support user2user (when we get
222 * support for that via an extension to the mechanism
223 * allowing for more than two security context tokens),
224 * and/or new unique MEMORY keytabs (we have MEMORY keytab
225 * support, but we don't have a keytab equivalent of
226 * krb5_cc_new_unique()). Either way, for now we can't
227 * support this.
229 *minor_status = ENOTSUP; /* XXX Better error? */
230 return GSS_S_FAILURE;
233 memset(&cred, 0, sizeof(cred));
235 if (handle->principal == NULL) {
236 kret = krb5_get_default_principal(context, &handle->principal);
237 if (kret)
238 goto end;
240 realm = krb5_principal_get_realm(context, handle->principal);
242 kret = krb5_get_init_creds_opt_alloc(context, &opt);
243 if (kret == 0) {
244 krb5_get_init_creds_opt_set_default_flags(context, "gss_krb5", realm,
245 opt);
246 kret = krb5_init_creds_init(context, handle->principal, NULL, NULL, 0,
247 opt, &ctx);
249 if (kret == 0)
250 kret = _krb5_init_creds_set_fast_anon_pkinit_optimistic(context, ctx);
251 if (kret == 0)
252 kret = krb5_init_creds_set_password(context, ctx, password);
255 * Get the current time before the AS exchange so we don't
256 * accidentally end up returning a value that puts advertised
257 * expiration past the real expiration.
259 * We need to do this because krb5_cc_get_lifetime() returns a
260 * relative time that we need to add to the current time. We ought
261 * to have a version of krb5_cc_get_lifetime() that returns absolute
262 * time...
264 krb5_timeofday(context, &now);
266 if (kret == 0)
267 kret = krb5_init_creds_get(context, ctx);
268 if (kret == 0)
269 kret = krb5_init_creds_get_creds(context, ctx, &cred);
270 if (kret == 0)
271 kret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, &ccache);
272 if (kret == 0)
273 kret = krb5_cc_initialize(context, ccache, cred.client);
274 if (kret == 0)
275 kret = krb5_init_creds_store(context, ctx, ccache);
276 if (kret == 0)
277 kret = krb5_cc_store_cred(context, ccache, &cred);
278 if (kret)
279 goto end;
281 handle->cred_flags |= GSS_CF_DESTROY_CRED_ON_RELEASE;
283 ret = __gsskrb5_ccache_lifetime(minor_status, context, ccache,
284 handle->principal, &left);
285 if (ret != GSS_S_COMPLETE)
286 goto end;
287 handle->endtime = now + left;
288 handle->ccache = ccache;
289 ccache = NULL;
290 ret = GSS_S_COMPLETE;
292 end:
293 krb5_get_init_creds_opt_free(context, opt);
294 if (ctx)
295 krb5_init_creds_free(context, ctx);
296 if (ccache != NULL)
297 krb5_cc_destroy(context, ccache);
298 if (cred.client != NULL)
299 krb5_free_cred_contents(context, &cred);
300 if (ret != GSS_S_COMPLETE)
301 *minor_status = kret;
302 return (ret);
306 * Acquires an initiator credential from a ccache or using a keytab.
308 static OM_uint32
309 acquire_initiator_cred(OM_uint32 *minor_status,
310 krb5_context context,
311 OM_uint32 time_req,
312 gss_OID_set desired_mechs,
313 gss_cred_usage_t cred_usage,
314 gss_const_key_value_set_t cred_store,
315 gsskrb5_cred handle)
317 OM_uint32 ret;
318 krb5_creds cred;
319 krb5_get_init_creds_opt *opt;
320 krb5_principal def_princ = NULL;
321 krb5_ccache def_ccache = NULL;
322 krb5_ccache ccache = NULL; /* we may store into this ccache */
323 krb5_keytab keytab = NULL;
324 krb5_error_code kret = 0;
325 OM_uint32 left;
326 const char *cs_ccache_name;
327 time_t lifetime = 0;
328 time_t now;
330 memset(&cred, 0, sizeof(cred));
332 ret = __gsskrb5_cred_store_find(minor_status, cred_store,
333 "ccache", &cs_ccache_name);
334 if (GSS_ERROR(ret))
335 return ret;
337 ret = GSS_S_FAILURE;
340 * Get current time early so we can set handle->endtime to a value that
341 * cannot accidentally be past the real endtime. We need a variant of
342 * krb5_cc_get_lifetime() that returns absolute endtime.
344 krb5_timeofday(context, &now);
347 * First look for a ccache that has the desired_name (which may be
348 * the default credential name), unless a specific credential cache
349 * was included in cred_store.
351 * If we don't have an unexpired credential, acquire one with a
352 * keytab.
354 * If we acquire one with a keytab, save it in the ccache we found
355 * with the expired credential, if any.
357 * If we don't have any such ccache, then use a MEMORY ccache.
360 if (handle->principal != NULL && cs_ccache_name == NULL) {
362 * Not default credential case. See if we can find a ccache in
363 * the cccol for the desired_name.
365 kret = krb5_cc_cache_match(context,
366 handle->principal,
367 &ccache);
368 if (kret == 0) {
369 kret = krb5_cc_get_lifetime(context, ccache, &lifetime);
370 if (kret == 0) {
371 if (lifetime > 0)
372 goto found;
373 else
374 goto try_keytab;
378 * Fall through. We shouldn't find this in the default ccache
379 * either, but we'll give it a try, then we'll try using a keytab.
384 * Either desired_name was GSS_C_NO_NAME (default cred) or
385 * krb5_cc_cache_match() failed (or found expired).
387 if (cs_ccache_name)
388 kret = krb5_cc_resolve(context, cs_ccache_name, &def_ccache);
389 else
390 kret = krb5_cc_default(context, &def_ccache);
391 if (kret != 0)
392 goto try_keytab;
393 kret = krb5_cc_get_lifetime(context, def_ccache, &lifetime);
394 if (kret != 0)
395 lifetime = 0;
396 kret = krb5_cc_get_principal(context, def_ccache, &def_princ);
397 if (kret != 0)
398 goto try_keytab;
400 * Have a default ccache; see if it matches desired_name.
402 if (handle->principal == NULL ||
403 krb5_principal_compare(context, handle->principal,
404 def_princ) == TRUE) {
406 * It matches.
408 * If we end up trying a keytab then we can write the result to
409 * the default ccache.
411 if (handle->principal == NULL) {
412 kret = krb5_copy_principal(context, def_princ, &handle->principal);
413 if (kret)
414 goto end;
416 if (ccache != NULL)
417 krb5_cc_close(context, ccache);
418 ccache = def_ccache;
419 def_ccache = NULL;
420 if (lifetime > 0)
421 goto found;
422 /* else we fall through and try using a keytab */
425 try_keytab:
426 if (handle->principal == NULL) {
427 /* We need to know what client principal to use */
428 kret = krb5_get_default_principal(context, &handle->principal);
429 if (kret)
430 goto end;
432 kret = get_client_keytab(context, cred_store, handle->principal, &keytab);
433 if (kret)
434 goto end;
436 kret = krb5_get_init_creds_opt_alloc(context, &opt);
437 if (kret)
438 goto end;
439 krb5_timeofday(context, &now);
440 kret = krb5_get_init_creds_keytab(context, &cred, handle->principal,
441 keytab, 0, NULL, opt);
442 krb5_get_init_creds_opt_free(context, opt);
443 if (kret)
444 goto end;
447 * We got a credential with a keytab. Save it if we can.
449 if (ccache == NULL) {
451 * There's no ccache we can overwrite with the credentials we acquired
452 * with a keytab. We'll use a MEMORY ccache then.
454 * Note that an application that falls into this repeatedly will do an
455 * AS exchange every time it acquires a credential handle. Hopefully
456 * this doesn't happen much. A workaround is to kinit -k once so that
457 * we always re-initialize the matched/default ccache here. I.e., once
458 * there's a FILE/DIR ccache, we'll keep it frash automatically if we
459 * have a keytab, but if there's no FILE/DIR ccache, then we'll
460 * get a fresh credential *every* time we're asked.
462 kret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, &ccache);
463 if (kret)
464 goto end;
465 handle->cred_flags |= GSS_CF_DESTROY_CRED_ON_RELEASE;
466 } /* else we'll re-initialize whichever ccache we matched above */
468 kret = krb5_cc_initialize(context, ccache, cred.client);
469 if (kret)
470 goto end;
471 kret = krb5_cc_store_cred(context, ccache, &cred);
472 if (kret)
473 goto end;
475 found:
476 assert(handle->principal != NULL);
477 ret = __gsskrb5_ccache_lifetime(minor_status, context, ccache,
478 handle->principal, &left);
479 if (ret != GSS_S_COMPLETE)
480 goto end;
481 handle->endtime = now + left;
482 handle->ccache = ccache;
483 ccache = NULL;
484 ret = GSS_S_COMPLETE;
485 kret = 0;
487 end:
488 if (ccache != NULL) {
489 if ((handle->cred_flags & GSS_CF_DESTROY_CRED_ON_RELEASE) != 0)
490 krb5_cc_destroy(context, ccache);
491 else
492 krb5_cc_close(context, ccache);
494 if (def_ccache != NULL)
495 krb5_cc_close(context, def_ccache);
496 if (cred.client != NULL)
497 krb5_free_cred_contents(context, &cred);
498 if (def_princ != NULL)
499 krb5_free_principal(context, def_princ);
500 if (keytab != NULL)
501 krb5_kt_close(context, keytab);
502 if (ret != GSS_S_COMPLETE && kret != 0)
503 *minor_status = kret;
504 return (ret);
507 static OM_uint32
508 acquire_acceptor_cred(OM_uint32 * minor_status,
509 krb5_context context,
510 OM_uint32 time_req,
511 gss_OID_set desired_mechs,
512 gss_cred_usage_t cred_usage,
513 gss_const_key_value_set_t cred_store,
514 gsskrb5_cred handle)
516 OM_uint32 ret;
517 krb5_error_code kret;
519 ret = GSS_S_FAILURE;
521 kret = get_system_keytab(context, cred_store, &handle->keytab);
522 if (kret)
523 goto end;
525 /* check that the requested principal exists in the keytab */
526 if (handle->principal) {
527 krb5_keytab_entry entry;
529 kret = krb5_kt_get_entry(context, handle->keytab,
530 handle->principal, 0, 0, &entry);
531 if (kret)
532 goto end;
533 krb5_kt_free_entry(context, &entry);
534 ret = GSS_S_COMPLETE;
535 } else {
537 * Check if there is at least one entry in the keytab before
538 * declaring it as an useful keytab.
540 krb5_keytab_entry tmp;
541 krb5_kt_cursor c;
543 kret = krb5_kt_start_seq_get (context, handle->keytab, &c);
544 if (kret)
545 goto end;
546 if (krb5_kt_next_entry(context, handle->keytab, &tmp, &c) == 0) {
547 krb5_kt_free_entry(context, &tmp);
548 ret = GSS_S_COMPLETE; /* ok found one entry */
550 krb5_kt_end_seq_get (context, handle->keytab, &c);
552 end:
553 if (ret != GSS_S_COMPLETE) {
554 if (handle->keytab != NULL)
555 krb5_kt_close(context, handle->keytab);
556 if (kret != 0) {
557 *minor_status = kret;
560 return (ret);
564 OM_uint32 GSSAPI_CALLCONV _gsskrb5_acquire_cred_from
565 (OM_uint32 * minor_status,
566 gss_const_name_t desired_name,
567 OM_uint32 time_req,
568 gss_OID_set desired_mechs,
569 gss_cred_usage_t cred_usage,
570 gss_const_key_value_set_t cred_store,
571 gss_cred_id_t * output_cred_handle,
572 gss_OID_set *actual_mechs,
573 OM_uint32 *time_rec
576 krb5_context context;
577 gsskrb5_cred handle;
578 OM_uint32 ret;
579 const char *password = NULL;
581 if (desired_mechs) {
582 int present = 0;
584 ret = gss_test_oid_set_member(minor_status, GSS_KRB5_MECHANISM,
585 desired_mechs, &present);
586 if (ret)
587 return ret;
588 if (!present) {
589 *minor_status = 0;
590 return GSS_S_BAD_MECH;
594 cred_usage &= GSS_C_OPTION_MASK;
596 if (cred_usage != GSS_C_ACCEPT && cred_usage != GSS_C_INITIATE &&
597 cred_usage != GSS_C_BOTH) {
598 *minor_status = GSS_KRB5_S_G_BAD_USAGE;
599 return GSS_S_FAILURE;
602 ret = __gsskrb5_cred_store_find(minor_status, cred_store,
603 "password", &password);
604 if (GSS_ERROR(ret))
605 return ret;
607 GSSAPI_KRB5_INIT(&context);
609 *output_cred_handle = GSS_C_NO_CREDENTIAL;
611 handle = calloc(1, sizeof(*handle));
612 if (handle == NULL) {
613 *minor_status = ENOMEM;
614 return GSS_S_FAILURE;
617 handle->destination_realm = NULL;
618 HEIMDAL_MUTEX_init(&handle->cred_id_mutex);
620 if (desired_name != GSS_C_NO_NAME) {
621 ret = _gsskrb5_canon_name(minor_status, context,
622 desired_name, &handle->principal);
623 if (ret) {
624 HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
625 free(handle);
626 return ret;
630 if (password) {
631 ret = acquire_cred_with_password(minor_status, context, password, time_req,
632 desired_mechs, cred_usage, cred_store, handle);
633 if (ret != GSS_S_COMPLETE) {
634 HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
635 krb5_free_principal(context, handle->principal);
636 free(handle);
637 return (ret);
639 } else {
641 * Acquire a credential from the specified or background credential
642 * store (ccache, keytab).
644 if (cred_usage == GSS_C_INITIATE || cred_usage == GSS_C_BOTH) {
645 ret = acquire_initiator_cred(minor_status, context, time_req,
646 desired_mechs, cred_usage,
647 cred_store, handle);
648 if (ret != GSS_S_COMPLETE) {
649 HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
650 krb5_free_principal(context, handle->principal);
651 free(handle);
652 return (ret);
655 if (cred_usage == GSS_C_ACCEPT || cred_usage == GSS_C_BOTH) {
656 ret = acquire_acceptor_cred(minor_status, context, time_req,
657 desired_mechs, cred_usage,
658 cred_store, handle);
659 if (ret != GSS_S_COMPLETE) {
660 HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
661 krb5_free_principal(context, handle->principal);
662 free(handle);
663 return (ret);
667 ret = gss_create_empty_oid_set(minor_status, &handle->mechanisms);
668 if (ret == GSS_S_COMPLETE)
669 ret = gss_add_oid_set_member(minor_status, GSS_KRB5_MECHANISM,
670 &handle->mechanisms);
671 handle->usage = cred_usage;
672 if (ret == GSS_S_COMPLETE)
673 ret = _gsskrb5_inquire_cred(minor_status, (gss_cred_id_t)handle,
674 NULL, time_rec, NULL, actual_mechs);
675 if (ret != GSS_S_COMPLETE) {
676 if (handle->mechanisms != NULL)
677 gss_release_oid_set(NULL, &handle->mechanisms);
678 HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
679 krb5_free_principal(context, handle->principal);
680 free(handle);
681 return (ret);
683 *minor_status = 0;
684 *output_cred_handle = (gss_cred_id_t)handle;
685 return (GSS_S_COMPLETE);