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