xref: /illumos-gate/usr/src/lib/gss_mechs/mech_krb5/mech/acquire_cred.c (revision 03100a6332bd4edc7a53091fcf7c9a7131bcdaa7)
1 /*
2  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 #pragma ident	"%Z%%M%	%I%	%E% SMI"
7 
8 /*
9  * Copyright 2000 by the Massachusetts Institute of Technology.
10  * All Rights Reserved.
11  *
12  * Export of this software from the United States of America may
13  *   require a specific license from the United States Government.
14  *   It is the responsibility of any person or organization contemplating
15  *   export to obtain such a license before exporting.
16  *
17  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
18  * distribute this software and its documentation for any purpose and
19  * without fee is hereby granted, provided that the above copyright
20  * notice appear in all copies and that both that copyright notice and
21  * this permission notice appear in supporting documentation, and that
22  * the name of M.I.T. not be used in advertising or publicity pertaining
23  * to distribution of the software without specific, written prior
24  * permission.  Furthermore if you modify this software you must label
25  * your software as modified software and not distribute it in such a
26  * fashion that it might be confused with the original M.I.T. software.
27  * M.I.T. makes no representations about the suitability of
28  * this software for any purpose.  It is provided "as is" without express
29  * or implied warranty.
30  *
31  */
32 /*
33  * Copyright 1993 by OpenVision Technologies, Inc.
34  *
35  * Permission to use, copy, modify, distribute, and sell this software
36  * and its documentation for any purpose is hereby granted without fee,
37  * provided that the above copyright notice appears in all copies and
38  * that both that copyright notice and this permission notice appear in
39  * supporting documentation, and that the name of OpenVision not be used
40  * in advertising or publicity pertaining to distribution of the software
41  * without specific, written prior permission. OpenVision makes no
42  * representations about the suitability of this software for any
43  * purpose.  It is provided "as is" without express or implied warranty.
44  *
45  * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
46  * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
47  * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR
48  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
49  * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
50  * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
51  * PERFORMANCE OF THIS SOFTWARE.
52  */
53 
54 /*
55  * Copyright (C) 1998 by the FundsXpress, INC.
56  *
57  * All rights reserved.
58  *
59  * Export of this software from the United States of America may require
60  * a specific license from the United States Government.  It is the
61  * responsibility of any person or organization contemplating export to
62  * obtain such a license before exporting.
63  *
64  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
65  * distribute this software and its documentation for any purpose and
66  * without fee is hereby granted, provided that the above copyright
67  * notice appear in all copies and that both that copyright notice and
68  * this permission notice appear in supporting documentation, and that
69  * the name of FundsXpress. not be used in advertising or publicity pertaining
70  * to distribution of the software without specific, written prior
71  * permission.  FundsXpress makes no representations about the suitability of
72  * this software for any purpose.  It is provided "as is" without express
73  * or implied warranty.
74  *
75  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
76  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
77  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
78  */
79 
80 #include "k5-int.h"
81 #include "gss_libinit.h"
82 #include "gssapiP_krb5.h"
83 #include "mglueP.h"
84 #ifdef HAVE_STRING_H
85 #include <string.h>
86 #else
87 #include <strings.h>
88 #endif
89 
90 /* SUNW15resync - Solaris kerberos does not need this feature in this file */
91 #ifdef USE_LOGIN_LIBRARY
92 #undef USE_LOGIN_LIBRARY
93 #endif
94 
95 #if defined(USE_LOGIN_LIBRARY)
96 #include <Kerberos/KerberosLoginPrivate.h>
97 #elif defined(USE_LEASH)
98 static void (*pLeash_AcquireInitialTicketsIfNeeded)(krb5_context,krb5_principal,char*,int) = NULL;
99 static HANDLE hLeashDLL = INVALID_HANDLE_VALUE;
100 #endif
101 
102 k5_mutex_t gssint_krb5_keytab_lock = K5_MUTEX_PARTIAL_INITIALIZER;
103 static char *krb5_gss_keytab = NULL;
104 
105 /* Heimdal calls this gsskrb5_register_acceptor_identity. */
106 OM_uint32 KRB5_CALLCONV
107 krb5_gss_register_acceptor_identity(const char *keytab)
108 {
109     size_t	len;
110     char *new, *old;
111     int err;
112 
113     err = gssint_initialize_library();
114     if (err != 0)
115 	return GSS_S_FAILURE;
116 
117     if (keytab == NULL)
118 	return GSS_S_FAILURE;
119 
120     len = strlen(keytab);
121     new = malloc(len + 1);
122     if (new == NULL)
123 	return GSS_S_FAILURE;
124     strcpy(new, keytab);
125 
126     err = k5_mutex_lock(&gssint_krb5_keytab_lock);
127     if (err) {
128 	free(new);
129 	return GSS_S_FAILURE;
130     }
131     old = krb5_gss_keytab;
132     krb5_gss_keytab = new;
133     k5_mutex_unlock(&gssint_krb5_keytab_lock);
134     if (old != NULL)
135 	free(old);
136     return GSS_S_COMPLETE;
137 }
138 
139 /* get credentials corresponding to a key in the krb5 keytab.
140    If the default name is requested, return the name in output_princ.
141      If output_princ is non-NULL, the caller will use or free it, regardless
142      of the return value.
143    If successful, set the keytab-specific fields in cred
144    */
145 
146 static OM_uint32
147 acquire_accept_cred(context, minor_status, desired_name, output_princ, cred)
148      krb5_context context;
149      OM_uint32 *minor_status;
150      gss_name_t desired_name;
151      krb5_principal *output_princ;
152      krb5_gss_cred_id_rec *cred;
153 {
154    krb5_error_code code;
155    krb5_principal princ;
156    krb5_keytab kt;
157    krb5_keytab_entry entry;
158 
159    *output_princ = NULL;
160    cred->keytab = NULL;
161 
162    /* open the default keytab */
163 
164    code = gssint_initialize_library();
165    if (code != 0) {
166        *minor_status = code;
167        return GSS_S_FAILURE;
168    }
169    code = k5_mutex_lock(&gssint_krb5_keytab_lock);
170    if (code) {
171        *minor_status = code;
172        return GSS_S_FAILURE;
173    }
174    if (krb5_gss_keytab != NULL) {
175       code = krb5_kt_resolve(context, krb5_gss_keytab, &kt);
176       k5_mutex_unlock(&gssint_krb5_keytab_lock);
177    } else {
178       k5_mutex_unlock(&gssint_krb5_keytab_lock);
179       code = krb5_kt_default(context, &kt);
180    }
181 
182    if (code) {
183       *minor_status = code;
184       /* Solaris Kerb NOTE: GSS_S_CRED_UNAVAIL is not RFC 2743 compliant */
185       return(GSS_S_NO_CRED);
186    }
187 
188    if (desired_name != GSS_C_NO_NAME) {
189       princ = (krb5_principal) desired_name;
190       if ((code = krb5_kt_get_entry(context, kt, princ, 0, 0, &entry))) {
191 	 (void) krb5_kt_close(context, kt);
192 	 if (code == KRB5_KT_NOTFOUND)
193 	    *minor_status = KG_KEYTAB_NOMATCH;
194 	 else
195 	    *minor_status = code;
196 	 /* Solaris Kerb NOTE: GSS_S_CRED_UNAVAIL is not RFC 2743 compliant */
197 	 return(GSS_S_NO_CRED);
198       }
199       krb5_kt_free_entry(context, &entry);
200 
201       /* Open the replay cache for this principal. */
202       if ((code = krb5_get_server_rcache(context,
203 					 krb5_princ_component(context, princ, 0),
204 					 &cred->rcache))) {
205 	 *minor_status = code;
206 	 return(GSS_S_FAILURE);
207       }
208 
209    }
210 
211 /* hooray.  we made it */
212 
213    cred->keytab = kt;
214 
215    return(GSS_S_COMPLETE);
216 }
217 
218 /* get credentials corresponding to the default credential cache.
219    If the default name is requested, return the name in output_princ.
220      If output_princ is non-NULL, the caller will use or free it, regardless
221      of the return value.
222    If successful, set the ccache-specific fields in cred.
223    */
224 
225 static OM_uint32
226 acquire_init_cred(context, minor_status, desired_name, output_princ, cred)
227      krb5_context context;
228      OM_uint32 *minor_status;
229      gss_name_t desired_name;
230      krb5_principal *output_princ;
231      krb5_gss_cred_id_rec *cred;
232 {
233    krb5_error_code code;
234    krb5_ccache ccache;
235    krb5_principal princ, tmp_princ;
236    krb5_flags flags;
237    krb5_cc_cursor cur;
238    krb5_creds creds;
239    int got_endtime;
240 
241    cred->ccache = NULL;
242 
243    /* load the GSS ccache name into the kg_context */
244 
245    if (GSS_ERROR(kg_sync_ccache_name(context, minor_status)))
246        return(GSS_S_FAILURE);
247 
248 #if defined(USE_LOGIN_LIBRARY) || defined(USE_LEASH)
249    if (desired_name != NULL) {
250 #if defined(USE_LOGIN_LIBRARY)
251        char *ccache_name = NULL;
252        KLPrincipal kl_desired_princ = NULL;
253 
254        if ((code = __KLCreatePrincipalFromKerberos5Principal ((krb5_principal) desired_name,
255                                                               &kl_desired_princ))) {
256            *minor_status = code;
257            return(GSS_S_NO_CRED);
258        }
259 
260        if ((code = KLAcquireInitialTickets (kl_desired_princ, NULL, NULL, &ccache_name))) {
261            KLDisposePrincipal (kl_desired_princ);
262            *minor_status = code;
263            return(GSS_S_NO_CRED);
264        }
265 
266        if ((code = krb5_cc_resolve (context, ccache_name, &ccache))) {
267            KLDisposeString (ccache_name);
268            KLDisposePrincipal (kl_desired_princ);
269            *minor_status = code;
270            return(GSS_S_NO_CRED);
271        }
272 
273        if (kl_desired_princ != NULL) { KLDisposePrincipal (kl_desired_princ); }
274        if (ccache_name      != NULL) { KLDisposeString (ccache_name); }
275 #elif defined(USE_LEASH)
276        if ( hLeashDLL == INVALID_HANDLE_VALUE ) {
277 	   hLeashDLL = LoadLibrary("leashw32.dll");
278 	   if ( hLeashDLL != INVALID_HANDLE_VALUE ) {
279 	       (FARPROC) pLeash_AcquireInitialTicketsIfNeeded =
280 		   GetProcAddress(hLeashDLL, "not_an_API_Leash_AcquireInitialTicketsIfNeeded");
281 	   }
282        }
283 
284        if ( pLeash_AcquireInitialTicketsIfNeeded ) {
285 	   char ccname[256]="";
286 	   pLeash_AcquireInitialTicketsIfNeeded(context, (krb5_principal) desired_name, ccname, sizeof(ccname));
287 	   if (!ccname[0]) {
288 	       *minor_status = KRB5_CC_NOTFOUND;
289 	       return(GSS_S_NO_CRED);
290 	   }
291 
292 	   if ((code = krb5_cc_resolve (context, ccname, &ccache))) {
293 	       *minor_status = code;
294 	       return(GSS_S_NO_CRED);
295 	   }
296        } else {
297 	   /* leash dll not available, open the default credential cache */
298 
299 	   if ((code = krb5int_cc_default(context, &ccache))) {
300 	       *minor_status = code;
301 	       return(GSS_S_NO_CRED);
302 	   }
303        }
304 #endif /* USE_LEASH */
305    } else
306 #endif /* USE_LOGIN_LIBRARY || USE_LEASH */
307    {
308        /* open the default credential cache */
309 
310        if ((code = krb5int_cc_default(context, &ccache))) {
311 	   *minor_status = code;
312 	   return(GSS_S_NO_CRED);
313        }
314    }
315 
316    /* turn off OPENCLOSE mode while extensive frobbing is going on */
317    /*
318     * SUNW14resync
319     * Added calls to krb5_cc_set_flags(... KRB5_TC_OPENCLOSE)
320     * on the error returns cuz the 1.4 krb5_cc_close does not always close
321     * the file like it used to and caused STC test gss.27 to fail.
322     */
323    flags = 0;		/* turns off OPENCLOSE mode */
324    if ((code = krb5_cc_set_flags(context, ccache, flags))) {
325       (void)krb5_cc_close(context, ccache);
326       *minor_status = code;
327       return(GSS_S_NO_CRED);
328    }
329 
330    /* get out the principal name and see if it matches */
331 
332    if ((code = krb5_cc_get_principal(context, ccache, &princ))) {
333       (void)krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE);
334       (void)krb5_cc_close(context, ccache);
335       *minor_status = code;
336       return(GSS_S_FAILURE);
337    }
338 
339    if (desired_name != (gss_name_t) NULL) {
340       if (! krb5_principal_compare(context, princ, (krb5_principal) desired_name)) {
341 	 (void)krb5_free_principal(context, princ);
342 	 (void)krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE);
343 	 (void)krb5_cc_close(context, ccache);
344 	 *minor_status = KG_CCACHE_NOMATCH;
345 	 return(GSS_S_NO_CRED);
346       }
347       (void)krb5_free_principal(context, princ);
348       princ = (krb5_principal) desired_name;
349    } else {
350       *output_princ = princ;
351    }
352 
353    /* iterate over the ccache, find the tgt */
354 
355    if ((code = krb5_cc_start_seq_get(context, ccache, &cur))) {
356       (void)krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE);
357       (void)krb5_cc_close(context, ccache);
358       *minor_status = code;
359       return(GSS_S_FAILURE);
360    }
361 
362    /* this is hairy.  If there's a tgt for the principal's local realm
363       in here, that's what we want for the expire time.  But if
364       there's not, then we want to use the first key.  */
365 
366    got_endtime = 0;
367 
368    code = krb5_build_principal_ext(context, &tmp_princ,
369 				   krb5_princ_realm(context, princ)->length,
370 				   krb5_princ_realm(context, princ)->data,
371 				   6, "krbtgt",
372 				   krb5_princ_realm(context, princ)->length,
373 				   krb5_princ_realm(context, princ)->data,
374 				   0);
375    if (code) {
376       (void)krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE);
377       (void)krb5_cc_close(context, ccache);
378       *minor_status = code;
379       return(GSS_S_FAILURE);
380    }
381    while (!(code = krb5_cc_next_cred(context, ccache, &cur, &creds))) {
382       if (krb5_principal_compare(context, tmp_princ, creds.server)) {
383 	 cred->tgt_expire = creds.times.endtime;
384 	 got_endtime = 1;
385 	 *minor_status = 0;
386 	 code = 0;
387 	 krb5_free_cred_contents(context, &creds);
388 	 break;
389       }
390       if (got_endtime == 0) {
391 	 cred->tgt_expire = creds.times.endtime;
392 	 got_endtime = 1;
393       }
394       krb5_free_cred_contents(context, &creds);
395    }
396    krb5_free_principal(context, tmp_princ);
397 
398    if (code && code != KRB5_CC_END) {
399       /* this means some error occurred reading the ccache */
400       (void)krb5_cc_end_seq_get(context, ccache, &cur);
401       (void)krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE);
402       (void)krb5_cc_close(context, ccache);
403       *minor_status = code;
404       return(GSS_S_FAILURE);
405    } else if (! got_endtime) {
406       /* this means the ccache was entirely empty */
407       (void)krb5_cc_end_seq_get(context, ccache, &cur);
408       (void)krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE);
409       (void)krb5_cc_close(context, ccache);
410       *minor_status = KG_EMPTY_CCACHE;
411       return(GSS_S_FAILURE);
412    } else {
413       /* this means that we found an endtime to use. */
414       if ((code = krb5_cc_end_seq_get(context, ccache, &cur))) {
415 	 (void)krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE);
416 	 (void)krb5_cc_close(context, ccache);
417 	 *minor_status = code;
418 	 return(GSS_S_FAILURE);
419       }
420       flags = KRB5_TC_OPENCLOSE;	/* turns on OPENCLOSE mode */
421       if ((code = krb5_cc_set_flags(context, ccache, flags))) {
422 	 (void)krb5_cc_close(context, ccache);
423 	 *minor_status = code;
424 	 return(GSS_S_FAILURE);
425       }
426    }
427 
428    /* the credentials match and are valid */
429 
430    cred->ccache = ccache;
431    /* minor_status is set while we are iterating over the ccache */
432    return(GSS_S_COMPLETE);
433 }
434 
435 /*ARGSUSED*/
436 OM_uint32
437 krb5_gss_acquire_cred(minor_status, desired_name, time_req,
438 		      desired_mechs, cred_usage, output_cred_handle,
439 		      actual_mechs, time_rec)
440      OM_uint32 *minor_status;
441      gss_name_t desired_name;
442      OM_uint32 time_req;
443      gss_OID_set desired_mechs;
444      gss_cred_usage_t cred_usage;
445      gss_cred_id_t *output_cred_handle;
446      gss_OID_set *actual_mechs;
447      OM_uint32 *time_rec;
448 {
449    krb5_context context;
450    size_t i;
451    krb5_gss_cred_id_t cred;
452    gss_OID_set ret_mechs;
453    int req_old, req_new;
454    OM_uint32 ret;
455    krb5_error_code code;
456 
457    code = gssint_initialize_library();
458    if (code) {
459        *minor_status = code;
460        return GSS_S_FAILURE;
461    }
462 
463    code = krb5_gss_init_context(&context);
464    if (code) {
465        *minor_status = code;
466        return GSS_S_FAILURE;
467    }
468 
469    /* make sure all outputs are valid */
470 
471    *output_cred_handle = NULL;
472    if (actual_mechs)
473       *actual_mechs = NULL;
474    if (time_rec)
475       *time_rec = 0;
476 
477    /* validate the name */
478 
479    /*SUPPRESS 29*/
480    if ((desired_name != (gss_name_t) NULL) &&
481        (! kg_validate_name(desired_name))) {
482       *minor_status = (OM_uint32) G_VALIDATE_FAILED;
483       krb5_free_context(context);
484       return(GSS_S_CALL_BAD_STRUCTURE|GSS_S_BAD_NAME);
485    }
486 
487    /* verify that the requested mechanism set is the default, or
488       contains krb5 */
489 
490    if (desired_mechs == GSS_C_NULL_OID_SET) {
491       req_old = 1;
492       req_new = 1;
493    } else {
494       req_old = 0;
495       req_new = 0;
496 
497       for (i=0; i<desired_mechs->count; i++) {
498 	 if (g_OID_equal(gss_mech_krb5_old, &(desired_mechs->elements[i])))
499 	    req_old++;
500 	 if (g_OID_equal(gss_mech_krb5, &(desired_mechs->elements[i])))
501 	    req_new++;
502       }
503 
504       if (!req_old && !req_new) {
505 	 *minor_status = 0;
506 	 krb5_free_context(context);
507 	 return(GSS_S_BAD_MECH);
508       }
509    }
510 
511    /* create the gss cred structure */
512 
513    if ((cred =
514 	(krb5_gss_cred_id_t) xmalloc(sizeof(krb5_gss_cred_id_rec))) == NULL) {
515       *minor_status = ENOMEM;
516       krb5_free_context(context);
517       return(GSS_S_FAILURE);
518    }
519    memset(cred, 0, sizeof(krb5_gss_cred_id_rec));
520 
521    cred->usage = cred_usage;
522    cred->princ = NULL;
523    cred->prerfc_mech = req_old;
524    cred->rfc_mech = req_new;
525 
526    cred->keytab = NULL;
527    cred->ccache = NULL;
528 
529    code = k5_mutex_init(&cred->lock);
530    if (code) {
531        *minor_status = code;
532        krb5_free_context(context);
533        return GSS_S_FAILURE;
534    }
535    /* Note that we don't need to lock this GSSAPI credential record
536       here, because no other thread can gain access to it until we
537       return it.  */
538 
539    if ((cred_usage != GSS_C_INITIATE) &&
540        (cred_usage != GSS_C_ACCEPT) &&
541        (cred_usage != GSS_C_BOTH)) {
542       k5_mutex_destroy(&cred->lock);
543       xfree(cred);
544       *minor_status = (OM_uint32) G_BAD_USAGE;
545       krb5_free_context(context);
546       return(GSS_S_FAILURE);
547    }
548 
549    /* if requested, acquire credentials for accepting */
550    /* this will fill in cred->princ if the desired_name is not specified */
551 
552    if ((cred_usage == GSS_C_ACCEPT) ||
553        (cred_usage == GSS_C_BOTH))
554       if ((ret = acquire_accept_cred(context, minor_status, desired_name,
555 				     &(cred->princ), cred))
556 	  != GSS_S_COMPLETE) {
557 	 if (cred->princ)
558 	    krb5_free_principal(context, cred->princ);
559          k5_mutex_destroy(&cred->lock);
560          xfree(cred);
561 	 /* minor_status set by acquire_accept_cred() */
562 	 krb5_free_context(context);
563 	 return(ret);
564       }
565 
566    /* if requested, acquire credentials for initiation */
567    /* this will fill in cred->princ if it wasn't set above, and
568       the desired_name is not specified */
569 
570    if ((cred_usage == GSS_C_INITIATE) ||
571        (cred_usage == GSS_C_BOTH))
572       if ((ret =
573 	   acquire_init_cred(context, minor_status,
574 			     cred->princ?(gss_name_t)cred->princ:desired_name,
575 			     &(cred->princ), cred))
576 	  != GSS_S_COMPLETE) {
577 	 if (cred->keytab)
578 	    krb5_kt_close(context, cred->keytab);
579 	 if (cred->princ)
580 	    krb5_free_principal(context, cred->princ);
581          k5_mutex_destroy(&cred->lock);
582          xfree(cred);
583 	 /* minor_status set by acquire_init_cred() */
584 	 krb5_free_context(context);
585 	 return(ret);
586       }
587 
588    /* Solaris Kerberos:
589     * if the princ wasn't filled in already, fill it in now unless
590     * a cred with no associated princ is requested (will invoke default
591     * behaviour when gss_accept_init_context() is called).
592     * Note MIT 1.4 has GSS_C_NO_CREDENTIAL instead of GSS_C_NO_NAME
593     */
594    if (!cred->princ && (desired_name != GSS_C_NO_NAME))
595       if ((code = krb5_copy_principal(context, (krb5_principal) desired_name,
596 				      &(cred->princ)))) {
597 	 if (cred->ccache)
598 	    (void)krb5_cc_close(context, cred->ccache);
599 	 if (cred->keytab)
600 	    (void)krb5_kt_close(context, cred->keytab);
601          k5_mutex_destroy(&cred->lock);
602          xfree(cred);
603 	 *minor_status = code;
604 	 krb5_free_context(context);
605 	 return(GSS_S_FAILURE);
606       }
607 
608    /*** at this point, the cred structure has been completely created */
609 
610    /* compute time_rec */
611 
612    if (cred_usage == GSS_C_ACCEPT) {
613       if (time_rec)
614 	 *time_rec = GSS_C_INDEFINITE;
615    } else {
616       krb5_timestamp now;
617 
618       if ((code = krb5_timeofday(context, &now))) {
619 	 if (cred->ccache)
620 	    (void)krb5_cc_close(context, cred->ccache);
621 	 if (cred->keytab)
622 	    (void)krb5_kt_close(context, cred->keytab);
623 	 if (cred->princ)
624 	    krb5_free_principal(context, cred->princ);
625          k5_mutex_destroy(&cred->lock);
626          xfree(cred);
627 	 *minor_status = code;
628 	 krb5_free_context(context);
629 	 return(GSS_S_FAILURE);
630       }
631 
632       if (time_rec)
633 	 *time_rec = (cred->tgt_expire > now) ? (cred->tgt_expire - now) : 0;
634    }
635 
636    /* create mechs */
637 
638    if (actual_mechs) {
639        if (GSS_ERROR(ret = generic_gss_create_empty_oid_set(minor_status,
640 							    &ret_mechs)) ||
641 	   (cred->prerfc_mech &&
642 	    GSS_ERROR(ret = generic_gss_add_oid_set_member(minor_status,
643 							(const gss_OID) gss_mech_krb5_old,
644 							   &ret_mechs))) ||
645 	   (cred->rfc_mech &&
646 	    GSS_ERROR(ret = generic_gss_add_oid_set_member(minor_status,
647 							(const gss_OID)	   gss_mech_krb5,
648 							   &ret_mechs)))) {
649 	   if (cred->ccache)
650 	       (void)krb5_cc_close(context, cred->ccache);
651 	   if (cred->keytab)
652 	       (void)krb5_kt_close(context, cred->keytab);
653 	   if (cred->princ)
654 	       krb5_free_principal(context, cred->princ);
655            k5_mutex_destroy(&cred->lock);
656 	   xfree(cred);
657 	   /* *minor_status set above */
658 	   krb5_free_context(context);
659 	   return(ret);
660        }
661    }
662 
663    /* intern the credential handle */
664 
665    if (! kg_save_cred_id((gss_cred_id_t) cred)) {
666       free(ret_mechs->elements);
667       free(ret_mechs);
668       if (cred->ccache)
669 	 (void)krb5_cc_close(context, cred->ccache);
670       if (cred->keytab)
671 	 (void)krb5_kt_close(context, cred->keytab);
672       if (cred->princ)
673 	 krb5_free_principal(context, cred->princ);
674       k5_mutex_destroy(&cred->lock);
675       xfree(cred);
676       *minor_status = (OM_uint32) G_VALIDATE_FAILED;
677       krb5_free_context(context);
678       return(GSS_S_FAILURE);
679    }
680 
681    /* return success */
682 
683    *minor_status = 0;
684    *output_cred_handle = (gss_cred_id_t) cred;
685    if (actual_mechs)
686       *actual_mechs = ret_mechs;
687 
688    krb5_free_context(context);
689    return(GSS_S_COMPLETE);
690 }
691