4c8c8c7ec7b3c93536608e4e2bed8fbb8480a2f3
[mech_eap.orig] / mech_eap / util_cred.c
1 /*
2  * Copyright (c) 2011, JANET(UK)
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * 3. Neither the name of JANET(UK) nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32
33 /*
34  * Utility routines for credential handles.
35  */
36
37 #include "gssapiP_eap.h"
38
39 #include <pwd.h>
40
41 OM_uint32
42 gssEapAllocCred(OM_uint32 *minor, gss_cred_id_t *pCred)
43 {
44     OM_uint32 tmpMinor;
45     gss_cred_id_t cred;
46
47     *pCred = GSS_C_NO_CREDENTIAL;
48
49     cred = (gss_cred_id_t)GSSEAP_CALLOC(1, sizeof(*cred));
50     if (cred == NULL) {
51         *minor = ENOMEM;
52         return GSS_S_FAILURE;
53     }
54
55     if (GSSEAP_MUTEX_INIT(&cred->mutex) != 0) {
56         *minor = errno;
57         gssEapReleaseCred(&tmpMinor, &cred);
58         return GSS_S_FAILURE;
59     }
60
61     *pCred = cred;
62
63     *minor = 0;
64     return GSS_S_COMPLETE;
65 }
66
67 static void
68 zeroAndReleasePassword(gss_buffer_t password)
69 {
70     if (password->value != NULL) {
71         memset(password->value, 0, password->length);
72         GSSEAP_FREE(password->value);
73     }
74
75     password->value = NULL;
76     password->length = 0;
77 }
78
79 OM_uint32
80 gssEapReleaseCred(OM_uint32 *minor, gss_cred_id_t *pCred)
81 {
82     OM_uint32 tmpMinor;
83     gss_cred_id_t cred = *pCred;
84     krb5_context krbContext = NULL;
85
86     if (cred == GSS_C_NO_CREDENTIAL) {
87         return GSS_S_COMPLETE;
88     }
89
90     GSSEAP_KRB_INIT(&krbContext);
91
92     gssEapReleaseName(&tmpMinor, &cred->name);
93     gssEapReleaseName(&tmpMinor, &cred->target);
94
95     zeroAndReleasePassword(&cred->password);
96
97     gss_release_buffer(&tmpMinor, &cred->radiusConfigFile);
98     gss_release_buffer(&tmpMinor, &cred->radiusConfigStanza);
99     gss_release_buffer(&tmpMinor, &cred->caCertificate);
100     gss_release_buffer(&tmpMinor, &cred->subjectNameConstraint);
101     gss_release_buffer(&tmpMinor, &cred->subjectAltNameConstraint);
102
103 #ifdef GSSEAP_ENABLE_REAUTH
104     if (cred->krbCredCache != NULL) {
105         if (cred->flags & CRED_FLAG_DEFAULT_CCACHE)
106             krb5_cc_close(krbContext, cred->krbCredCache);
107         else
108             krb5_cc_destroy(krbContext, cred->krbCredCache);
109     }
110     if (cred->reauthCred != GSS_C_NO_CREDENTIAL)
111         gssReleaseCred(&tmpMinor, &cred->reauthCred);
112 #endif
113
114     GSSEAP_MUTEX_DESTROY(&cred->mutex);
115     memset(cred, 0, sizeof(*cred));
116     GSSEAP_FREE(cred);
117     *pCred = NULL;
118
119     *minor = 0;
120     return GSS_S_COMPLETE;
121 }
122
123 static OM_uint32
124 readStaticIdentityFile(OM_uint32 *minor,
125                        gss_buffer_t defaultIdentity,
126                        gss_buffer_t defaultPassword)
127 {
128     OM_uint32 major, tmpMinor;
129     FILE *fp = NULL;
130     char pwbuf[BUFSIZ], buf[BUFSIZ];
131     char *ccacheName;
132     struct passwd *pw = NULL, pwd;
133     int i = 0;
134
135     defaultIdentity->length = 0;
136     defaultIdentity->value = NULL;
137
138     if (defaultPassword != GSS_C_NO_BUFFER) {
139         defaultPassword->length = 0;
140         defaultPassword->value = NULL;
141     }
142
143     ccacheName = getenv("GSSEAP_IDENTITY");
144     if (ccacheName == NULL) {
145         if (getpwuid_r(getuid(), &pwd, pwbuf, sizeof(pwbuf), &pw) != 0 ||
146             pw == NULL || pw->pw_dir == NULL) {
147             major = GSS_S_CRED_UNAVAIL;
148             *minor = errno;
149             goto cleanup;
150         }
151
152         snprintf(buf, sizeof(buf), "%s/.gss_eap_id", pw->pw_dir);
153         ccacheName = buf;
154     }
155
156     fp = fopen(ccacheName, "r");
157     if (fp == NULL) {
158         major = GSS_S_CRED_UNAVAIL;
159         *minor = GSSEAP_NO_DEFAULT_CRED;
160         goto cleanup;
161     }
162
163     while (fgets(buf, sizeof(buf), fp) != NULL) {
164         gss_buffer_desc src, *dst;
165
166         src.length = strlen(buf);
167         src.value = buf;
168
169         if (src.length == 0)
170             break;
171
172         if (buf[src.length - 1] == '\n') {
173             buf[src.length - 1] = '\0';
174             if (--src.length == 0)
175                 break;
176         }
177
178         if (i == 0)
179             dst = defaultIdentity;
180         else if (i == 1)
181             dst = defaultPassword;
182         else
183             break;
184
185         if (dst != GSS_C_NO_BUFFER) {
186             major = duplicateBuffer(minor, &src, dst);
187             if (GSS_ERROR(major))
188                 goto cleanup;
189         }
190
191         i++;
192     }
193
194     if (defaultIdentity->length == 0) {
195         major = GSS_S_CRED_UNAVAIL;
196         *minor = GSSEAP_NO_DEFAULT_CRED;
197         goto cleanup;
198     }
199
200     major = GSS_S_COMPLETE;
201     *minor = 0;
202
203 cleanup:
204     if (fp != NULL)
205         fclose(fp);
206
207     if (GSS_ERROR(major)) {
208         gss_release_buffer(&tmpMinor, defaultIdentity);
209         zeroAndReleasePassword(defaultPassword);
210     }
211
212     memset(buf, 0, sizeof(buf));
213
214     return major;
215 }
216
217 gss_OID
218 gssEapPrimaryMechForCred(gss_cred_id_t cred)
219 {
220     gss_OID nameMech = GSS_C_NO_OID;
221
222     if (cred->mechanisms != GSS_C_NO_OID_SET &&
223         cred->mechanisms->count == 1)
224         nameMech = &cred->mechanisms->elements[0];
225
226     return nameMech;
227 }
228
229 OM_uint32
230 gssEapAcquireCred(OM_uint32 *minor,
231                   const gss_name_t desiredName,
232                   OM_uint32 timeReq GSSEAP_UNUSED,
233                   const gss_OID_set desiredMechs,
234                   int credUsage,
235                   gss_cred_id_t *pCred,
236                   gss_OID_set *pActualMechs,
237                   OM_uint32 *timeRec)
238 {
239     OM_uint32 major, tmpMinor;
240     gss_cred_id_t cred;
241
242     /* XXX TODO validate with changed set_cred_option API */
243     *pCred = GSS_C_NO_CREDENTIAL;
244
245     major = gssEapAllocCred(minor, &cred);
246     if (GSS_ERROR(major))
247         goto cleanup;
248
249     switch (credUsage) {
250     case GSS_C_BOTH:
251         cred->flags |= CRED_FLAG_INITIATE | CRED_FLAG_ACCEPT;
252         break;
253     case GSS_C_INITIATE:
254         cred->flags |= CRED_FLAG_INITIATE;
255         break;
256     case GSS_C_ACCEPT:
257         cred->flags |= CRED_FLAG_ACCEPT;
258         break;
259     default:
260         major = GSS_S_FAILURE;
261         *minor = GSSEAP_BAD_USAGE;
262         goto cleanup;
263         break;
264     }
265
266     major = gssEapValidateMechs(minor, desiredMechs);
267     if (GSS_ERROR(major))
268         goto cleanup;
269
270     major = duplicateOidSet(minor, desiredMechs, &cred->mechanisms);
271     if (GSS_ERROR(major))
272         goto cleanup;
273
274     if (desiredName != GSS_C_NO_NAME) {
275         GSSEAP_MUTEX_LOCK(&desiredName->mutex);
276
277         major = gssEapDuplicateName(minor, desiredName, &cred->name);
278         if (GSS_ERROR(major)) {
279             GSSEAP_MUTEX_UNLOCK(&desiredName->mutex);
280             goto cleanup;
281         }
282
283         GSSEAP_MUTEX_UNLOCK(&desiredName->mutex);
284     }
285
286     if (pActualMechs != NULL) {
287         major = duplicateOidSet(minor, cred->mechanisms, pActualMechs);
288         if (GSS_ERROR(major))
289             goto cleanup;
290     }
291
292     if (timeRec != NULL)
293         *timeRec = GSS_C_INDEFINITE;
294
295     *pCred = cred;
296
297     major = GSS_S_COMPLETE;
298     *minor = 0;
299
300 cleanup:
301     if (GSS_ERROR(major))
302         gssEapReleaseCred(&tmpMinor, &cred);
303
304     return major;
305 }
306
307 /*
308  * Return TRUE if cred available for mechanism. Caller need no acquire
309  * lock because mechanisms list is immutable.
310  */
311 int
312 gssEapCredAvailable(gss_cred_id_t cred, gss_OID mech)
313 {
314     OM_uint32 minor;
315     int present = 0;
316
317     assert(mech != GSS_C_NO_OID);
318
319     if (cred == GSS_C_NO_CREDENTIAL || cred->mechanisms == GSS_C_NO_OID_SET)
320         return TRUE;
321
322     gss_test_oid_set_member(&minor, mech, cred->mechanisms, &present);
323
324     return present;
325 }
326
327 static OM_uint32
328 staticIdentityFileResolveDefaultIdentity(OM_uint32 *minor,
329                                          const gss_cred_id_t cred,
330                                          gss_name_t *pName)
331 {
332     OM_uint32 major, tmpMinor;
333     gss_OID nameMech = gssEapPrimaryMechForCred(cred);
334     gss_buffer_desc defaultIdentity = GSS_C_EMPTY_BUFFER;
335
336     *pName = GSS_C_NO_NAME;
337
338     major = readStaticIdentityFile(minor, &defaultIdentity, GSS_C_NO_BUFFER);
339     if (major == GSS_S_COMPLETE) {
340         major = gssEapImportName(minor, &defaultIdentity, GSS_C_NT_USER_NAME,
341                                  nameMech, pName);
342     }
343
344     gss_release_buffer(&tmpMinor, &defaultIdentity);
345
346     return major;
347 }
348
349 static OM_uint32
350 gssEapResolveCredIdentity(OM_uint32 *minor,
351                           gss_cred_id_t cred)
352 {
353     OM_uint32 major;
354     gss_OID nameMech = gssEapPrimaryMechForCred(cred);
355
356     if (cred->name != GSS_C_NO_NAME) {
357         *minor = 0;
358         return GSS_S_COMPLETE;
359     }
360
361     if (cred->flags & CRED_FLAG_ACCEPT) {
362         gss_buffer_desc nameBuf = GSS_C_EMPTY_BUFFER;
363         char serviceName[5 + MAXHOSTNAMELEN];
364
365         /* default host-based service is host@localhost */
366         memcpy(serviceName, "host@", 5);
367         if (gethostname(&serviceName[5], MAXHOSTNAMELEN) != 0) {
368             *minor = GSSEAP_NO_HOSTNAME;
369             return GSS_S_FAILURE;
370         }
371
372         nameBuf.value = serviceName;
373         nameBuf.length = strlen((char *)nameBuf.value);
374
375         major = gssEapImportName(minor, &nameBuf, GSS_C_NT_HOSTBASED_SERVICE,
376                                  nameMech, &cred->name);
377         if (GSS_ERROR(major))
378             return major;
379     } else if (cred->flags & CRED_FLAG_INITIATE) {
380 #ifdef HAVE_MOONSHOT_GET_IDENTITY
381         major = libMoonshotResolveDefaultIdentity(minor, cred, &cred->name);
382         if (major == GSS_S_CRED_UNAVAIL)
383 #endif
384             major = staticIdentityFileResolveDefaultIdentity(minor, cred, &cred->name);
385         if (major != GSS_S_CRED_UNAVAIL)
386             return major;
387     }
388
389     *minor = 0;
390     return GSS_S_COMPLETE;
391 }
392
393 OM_uint32
394 gssEapInquireCred(OM_uint32 *minor,
395                   gss_cred_id_t cred,
396                   gss_name_t *name,
397                   OM_uint32 *pLifetime,
398                   gss_cred_usage_t *cred_usage,
399                   gss_OID_set *mechanisms)
400 {
401     OM_uint32 major;
402     time_t now, lifetime;
403
404     if (name != NULL) {
405         major = gssEapResolveCredIdentity(minor, cred);
406         if (GSS_ERROR(major))
407             goto cleanup;
408
409         if (cred->name != GSS_C_NO_NAME) {
410             major = gssEapDuplicateName(minor, cred->name, name);
411             if (GSS_ERROR(major))
412                 goto cleanup;
413         } else
414             *name = GSS_C_NO_NAME;
415     }
416
417     if (cred_usage != NULL) {
418         OM_uint32 flags = (cred->flags & (CRED_FLAG_INITIATE | CRED_FLAG_ACCEPT));
419
420         switch (flags) {
421         case CRED_FLAG_INITIATE:
422             *cred_usage = GSS_C_INITIATE;
423             break;
424         case CRED_FLAG_ACCEPT:
425             *cred_usage = GSS_C_ACCEPT;
426             break;
427         default:
428             *cred_usage = GSS_C_BOTH;
429             break;
430         }
431     }
432
433     if (mechanisms != NULL) {
434         if (cred->mechanisms != GSS_C_NO_OID_SET)
435             major = duplicateOidSet(minor, cred->mechanisms, mechanisms);
436         else
437             major = gssEapIndicateMechs(minor, mechanisms);
438         if (GSS_ERROR(major))
439             goto cleanup;
440     }
441
442     if (cred->expiryTime == 0) {
443         lifetime = GSS_C_INDEFINITE;
444     } else  {
445         now = time(NULL);
446         lifetime = now - cred->expiryTime;
447         if (lifetime < 0)
448             lifetime = 0;
449     }
450
451     if (pLifetime != NULL) {
452         *pLifetime = lifetime;
453     }
454
455     if (lifetime == 0) {
456         major = GSS_S_CREDENTIALS_EXPIRED;
457         *minor = GSSEAP_CRED_EXPIRED;
458         goto cleanup;
459     }
460
461     major = GSS_S_COMPLETE;
462     *minor = 0;
463
464 cleanup:
465     return major;
466 }
467
468 OM_uint32
469 gssEapSetCredPassword(OM_uint32 *minor,
470                       gss_cred_id_t cred,
471                       const gss_buffer_t password)
472 {
473     OM_uint32 major, tmpMinor;
474     gss_buffer_desc newPassword = GSS_C_EMPTY_BUFFER;
475
476     if (cred->flags & CRED_FLAG_RESOLVED) {
477         major = GSS_S_FAILURE;
478         *minor = GSSEAP_CRED_RESOLVED;
479         goto cleanup;
480     }
481
482     if (password != GSS_C_NO_BUFFER) {
483         major = duplicateBuffer(minor, password, &newPassword);
484         if (GSS_ERROR(major))
485             goto cleanup;
486
487         cred->flags |= CRED_FLAG_PASSWORD;
488     } else {
489         cred->flags &= ~(CRED_FLAG_PASSWORD);
490     }
491
492     gss_release_buffer(&tmpMinor, &cred->password);
493     cred->password = newPassword;
494
495     major = GSS_S_COMPLETE;
496     *minor = 0;
497
498 cleanup:
499     return major;
500 }
501
502 static OM_uint32
503 gssEapDuplicateCred(OM_uint32 *minor,
504                     const gss_cred_id_t src,
505                     gss_cred_id_t *pDst)
506 {
507     OM_uint32 major, tmpMinor;
508     gss_cred_id_t dst = GSS_C_NO_CREDENTIAL;
509
510     *pDst = GSS_C_NO_CREDENTIAL;
511
512     major = gssEapAllocCred(minor, &dst);
513     if (GSS_ERROR(major))
514         goto cleanup;
515
516     dst->flags = src->flags;
517
518     if (src->name != GSS_C_NO_NAME) {
519         major = gssEapDuplicateName(minor, src->name, &dst->name);
520         if (GSS_ERROR(major))
521             goto cleanup;
522     }
523
524     if (src->target != GSS_C_NO_NAME) {
525         major = gssEapDuplicateName(minor, src->target, &dst->target);
526         if (GSS_ERROR(major))
527             goto cleanup;
528     }
529
530     if (src->password.value != NULL) {
531         major = duplicateBuffer(minor, &src->password, &dst->password);
532         if (GSS_ERROR(major))
533             goto cleanup;
534     }
535
536     major = duplicateOidSet(minor, src->mechanisms, &dst->mechanisms);
537     if (GSS_ERROR(major))
538         goto cleanup;
539
540     dst->expiryTime = src->expiryTime;
541
542     if (src->radiusConfigFile.value != NULL)
543         duplicateBufferOrCleanup(&src->radiusConfigFile, &dst->radiusConfigFile);
544     if (src->radiusConfigStanza.value != NULL)
545         duplicateBufferOrCleanup(&src->radiusConfigStanza, &dst->radiusConfigStanza);
546     if (src->caCertificate.value != NULL)
547         duplicateBufferOrCleanup(&src->caCertificate, &dst->caCertificate);
548     if (src->subjectNameConstraint.value != NULL)
549         duplicateBufferOrCleanup(&src->subjectNameConstraint, &dst->subjectNameConstraint);
550     if (src->subjectAltNameConstraint.value != NULL)
551         duplicateBufferOrCleanup(&src->subjectAltNameConstraint, &dst->subjectAltNameConstraint);
552
553 #ifdef GSSEAP_ENABLE_REAUTH
554     /* XXX krbCredCache, reauthCred */
555 #endif
556
557     *pDst = dst;
558     dst = GSS_C_NO_CREDENTIAL;
559
560     major = GSS_S_COMPLETE;
561     *minor = 0;
562
563 cleanup:
564     gssEapReleaseCred(&tmpMinor, &dst);
565
566     return major;
567 }
568
569 static OM_uint32
570 staticIdentityFileResolveInitiatorCred(OM_uint32 *minor, gss_cred_id_t cred)
571 {
572     OM_uint32 major, tmpMinor;
573     gss_buffer_desc defaultIdentity = GSS_C_EMPTY_BUFFER;
574     gss_name_t defaultIdentityName = GSS_C_NO_NAME;
575     gss_buffer_desc defaultPassword = GSS_C_EMPTY_BUFFER;
576     int isDefaultIdentity = FALSE;
577
578     major = readStaticIdentityFile(minor, &defaultIdentity, &defaultPassword);
579     if (GSS_ERROR(major))
580         goto cleanup;
581
582     major = gssEapImportName(minor, &defaultIdentity, GSS_C_NT_USER_NAME,
583                              gssEapPrimaryMechForCred(cred), &defaultIdentityName);
584     if (GSS_ERROR(major))
585         goto cleanup;
586
587     if (defaultIdentityName == GSS_C_NO_NAME) {
588         if (cred->name == GSS_C_NO_NAME) {
589             major = GSS_S_CRED_UNAVAIL;
590             *minor = GSSEAP_NO_DEFAULT_IDENTITY;
591             goto cleanup;
592         }
593     } else {
594         if (cred->name == GSS_C_NO_NAME) {
595             cred->name = defaultIdentityName;
596             defaultIdentityName = GSS_C_NO_NAME;
597             isDefaultIdentity = TRUE;
598         } else {
599             major = gssEapCompareName(minor, cred->name,
600                                       defaultIdentityName, &isDefaultIdentity);
601             if (GSS_ERROR(major))
602                 goto cleanup;
603         }
604     }
605
606     if (isDefaultIdentity &&
607         (cred->flags & CRED_FLAG_PASSWORD) == 0) {
608         major = gssEapSetCredPassword(minor, cred, &defaultPassword);
609         if (GSS_ERROR(major))
610             goto cleanup;
611     }
612
613 cleanup:
614     gssEapReleaseName(&tmpMinor, &defaultIdentityName);
615     zeroAndReleasePassword(&defaultPassword);
616     gss_release_buffer(&tmpMinor, &defaultIdentity);
617
618     return major;
619 }
620
621 OM_uint32
622 gssEapResolveInitiatorCred(OM_uint32 *minor,
623                            const gss_cred_id_t cred,
624                            const gss_name_t targetName
625 #ifndef HAVE_MOONSHOT_GET_IDENTITY
626                                                        GSSEAP_UNUSED
627 #endif
628                            ,
629                            gss_cred_id_t *pResolvedCred)
630 {
631     OM_uint32 major, tmpMinor;
632     gss_cred_id_t resolvedCred = GSS_C_NO_CREDENTIAL;
633
634     if (cred == GSS_C_NO_CREDENTIAL) {
635         major = gssEapAcquireCred(minor,
636                                   GSS_C_NO_NAME,
637                                   GSS_C_INDEFINITE,
638                                   GSS_C_NO_OID_SET,
639                                   GSS_C_INITIATE,
640                                   &resolvedCred,
641                                   NULL,
642                                   NULL);
643         if (GSS_ERROR(major))
644             goto cleanup;
645     } else {
646         if ((cred->flags & CRED_FLAG_INITIATE) == 0) {
647             major = GSS_S_NO_CRED;
648             *minor = GSSEAP_CRED_USAGE_MISMATCH;
649             goto cleanup;
650         }
651
652         major = gssEapDuplicateCred(minor, cred, &resolvedCred);
653         if (GSS_ERROR(major))
654             goto cleanup;
655     }
656
657     if ((resolvedCred->flags & CRED_FLAG_RESOLVED) == 0) {
658 #ifdef HAVE_MOONSHOT_GET_IDENTITY
659         major = libMoonshotResolveInitiatorCred(minor, resolvedCred, targetName);
660         if (major == GSS_S_CRED_UNAVAIL)
661 #endif
662             major = staticIdentityFileResolveInitiatorCred(minor, resolvedCred);
663         if (GSS_ERROR(major))
664             goto cleanup;
665
666         if ((resolvedCred->flags & CRED_FLAG_PASSWORD) == 0) {
667             major = GSS_S_CRED_UNAVAIL;
668             *minor = GSSEAP_NO_DEFAULT_CRED;
669             goto cleanup;
670         }
671
672         resolvedCred->flags |= CRED_FLAG_RESOLVED;
673     }
674
675     *pResolvedCred = resolvedCred;
676     resolvedCred = GSS_C_NO_CREDENTIAL;
677
678     major = GSS_S_COMPLETE;
679     *minor = 0;
680
681 cleanup:
682     gssEapReleaseCred(&tmpMinor, &resolvedCred);
683
684     return major;
685 }