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