2 * Modifications for Lustre
4 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
6 * Author: Eric Mei <ericm@clusterfs.com>
10 * Adapted in part from MIT Kerberos 5-1.2.1 slave/kprop.c and from
11 * http://docs.sun.com/?p=/doc/816-1331/6m7oo9sms&a=view
13 * Copyright (c) 2002-2004 The Regents of the University of Michigan.
14 * All rights reserved.
16 * Andy Adamson <andros@umich.edu>
17 * J. Bruce Fields <bfields@umich.edu>
18 * Marius Aamodt Eriksen <marius@umich.edu>
19 * Kevin Coffman <kwc@umich.edu>
25 * Copyright 1990,1991 by the Massachusetts Institute of Technology.
26 * All Rights Reserved.
28 * Export of this software from the United States of America may
29 * require a specific license from the United States Government.
30 * It is the responsibility of any person or organization contemplating
31 * export to obtain such a license before exporting.
33 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
34 * distribute this software and its documentation for any purpose and
35 * without fee is hereby granted, provided that the above copyright
36 * notice appear in all copies and that both that copyright notice and
37 * this permission notice appear in supporting documentation, and that
38 * the name of M.I.T. not be used in advertising or publicity pertaining
39 * to distribution of the software without specific, written prior
40 * permission. Furthermore if you modify this software you must label
41 * your software as modified software and not distribute it in such a
42 * fashion that it might be confused with the original M.I.T. software.
43 * M.I.T. makes no representations about the suitability of
44 * this software for any purpose. It is provided "as is" without express
45 * or implied warranty.
49 * Copyright 1994 by OpenVision Technologies, Inc.
51 * Permission to use, copy, modify, distribute, and sell this software
52 * and its documentation for any purpose is hereby granted without fee,
53 * provided that the above copyright notice appears in all copies and
54 * that both that copyright notice and this permission notice appear in
55 * supporting documentation, and that the name of OpenVision not be used
56 * in advertising or publicity pertaining to distribution of the software
57 * without specific, written prior permission. OpenVision makes no
58 * representations about the suitability of this software for any
59 * purpose. It is provided "as is" without express or implied warranty.
61 * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
62 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
63 * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR
64 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
65 * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
66 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
67 * PERFORMANCE OF THIS SOFTWARE.
72 Copyright (c) 2004 The Regents of the University of Michigan.
75 Redistribution and use in source and binary forms, with or without
76 modification, are permitted provided that the following conditions
79 1. Redistributions of source code must retain the above copyright
80 notice, this list of conditions and the following disclaimer.
81 2. Redistributions in binary form must reproduce the above copyright
82 notice, this list of conditions and the following disclaimer in the
83 documentation and/or other materials provided with the distribution.
84 3. Neither the name of the University nor the names of its
85 contributors may be used to endorse or promote products derived
86 from this software without specific prior written permission.
88 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
89 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
90 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
91 DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
92 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
93 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
94 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
95 BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
96 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
97 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
98 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
106 #include <sys/param.h>
107 //#include <rpc/rpc.h>
108 #include <sys/types.h>
109 #include <sys/stat.h>
110 #include <sys/utsname.h>
111 #include <sys/socket.h>
112 #include <arpa/inet.h>
125 #include <gssapi/gssapi.h>
126 #ifdef USE_PRIVATE_KRB5_FUNCTIONS
127 #include <gssapi/gssapi_krb5.h>
131 #include "lsupport.h"
132 #include "lgss_utils.h"
133 #include "lgss_krb5_utils.h"
135 static void lgss_krb5_mutex_lock(void)
137 if (lgss_mutex_lock(LGSS_MUTEX_KRB5)) {
138 logmsg(LL_ERR, "can't lock process, abort!\n");
143 static void lgss_krb5_mutex_unlock(void)
145 if (lgss_mutex_unlock(LGSS_MUTEX_KRB5)) {
146 logmsg(LL_WARN, "can't unlock process, other processes "
147 "might need to wait long time\n");
153 * - currently we only support "normal" cache types: "FILE" and "MEMORY".
156 #define krb5_err_msg(code) error_message(code)
158 const char *krb5_cc_type_mem = "MEMORY:";
159 const char *krb5_cc_type_file = "FILE:";
160 const char *krb5_cred_root_suffix = "lustre_root";
161 const char *krb5_cred_mds_suffix = "lustre_mds";
162 const char *krb5_cred_oss_suffix = "lustre_oss";
164 char *krb5_this_realm = NULL;
165 char *krb5_keytab_file = "/etc/krb5.keytab";
166 char *krb5_cc_type = "FILE:";
167 char *krb5_cc_dir = "/tmp";
168 char *krb5_cred_prefix = "krb5cc_";
170 struct lgss_krb5_cred {
172 int kc_remove; /* remove cache upon release */
176 int lgss_krb5_set_ccache_name(const char *ccname)
178 #ifdef USE_GSS_KRB5_CCACHE_NAME
179 unsigned int maj_stat, min_stat;
181 maj_stat = gss_krb5_ccache_name(&min_stat, ccname, NULL);
182 if (maj_stat != GSS_S_COMPLETE) {
183 logmsg(LL_ERR, "failed to set ccache name\n");
188 * Set the KRB5CCNAME environment variable to tell the krb5 code
189 * which credentials cache to use. (Instead of using the private
190 * function above for which there is no generic gssapi equivalent)
192 if (setenv("KRB5CCNAME", ccname, 1)) {
193 logmsg(LL_ERR, "set env of krb5 ccname: %s\n",
198 logmsg(LL_DEBUG, "set cc: %s\n", ccname);
203 int lgss_krb5_get_local_realm(void)
205 krb5_context context = NULL;
206 krb5_error_code code;
209 if (krb5_this_realm != NULL)
212 code = krb5_init_context(&context);
214 logmsg(LL_ERR, "init ctx: %s\n", krb5_err_msg(code));
218 code = krb5_get_default_realm(context, &krb5_this_realm);
220 logmsg(LL_ERR, "get default realm: %s\n", krb5_err_msg(code));
224 logmsg(LL_DEBUG, "Local realm: %s\n", krb5_this_realm);
227 krb5_free_context(context);
232 int princ_is_local_realm(krb5_context ctx, krb5_principal princ)
234 return (lgss_krb5_strcasecmp(krb5_princ_realm(ctx, princ),
235 krb5_this_realm) == 0);
239 int svc_princ_verify_host(krb5_context ctx,
240 krb5_principal princ,
244 struct utsname utsbuf;
245 struct hostent *host;
246 const int max_namelen = 512;
247 char namebuf[max_namelen];
250 if (krb5_princ_component(ctx, princ, 1) == NULL) {
251 logmsg(loglevel, "service principal has no host part\n");
256 if (lnet_nid2hostname(self_nid, namebuf, max_namelen)) {
258 "can't resolve hostname from nid %"PRIx64"\n",
264 if (uname(&utsbuf)) {
265 logmsg(loglevel, "get UTS name: %s\n", strerror(errno));
269 host = gethostbyname(utsbuf.nodename);
271 logmsg(loglevel, "failed to get local hostname\n");
274 h_name = host->h_name;
277 if (lgss_krb5_strcasecmp(krb5_princ_component(ctx, princ, 1),
279 logmsg(loglevel, "service principal: hostname %.*s "
280 "doesn't match localhost %s\n",
281 krb5_princ_component(ctx, princ, 1)->length,
282 krb5_princ_component(ctx, princ, 1)->data,
291 int lkrb5_cc_check_tgt_princ(krb5_context ctx,
293 krb5_principal princ,
297 const char *princ_name;
299 logmsg(LL_DEBUG, "principal: realm %.*s, type %d, size %d, name %.*s\n",
300 krb5_princ_realm(ctx, princ)->length,
301 krb5_princ_realm(ctx, princ)->data,
302 krb5_princ_type(ctx, princ),
303 krb5_princ_size(ctx, princ),
304 krb5_princ_name(ctx, princ)->length,
305 krb5_princ_name(ctx, princ)->data);
308 if (krb5_princ_type(ctx, princ) != KRB5_NT_PRINCIPAL) {
309 logmsg(LL_WARN, "principal type %d is not I want\n",
310 krb5_princ_type(ctx, princ));
314 /* check local realm */
315 if (!princ_is_local_realm(ctx, princ)) {
316 logmsg(LL_WARN, "principal realm %.*s not local: %s\n",
317 krb5_princ_realm(ctx, princ)->length,
318 krb5_princ_realm(ctx, princ)->data,
323 /* check principal name */
325 case LGSS_ROOT_CRED_ROOT:
326 princ_name = LGSS_USR_ROOT_STR;
328 case LGSS_ROOT_CRED_MDT:
329 princ_name = LGSS_SVC_MDS_STR;
331 case LGSS_ROOT_CRED_OST:
332 princ_name = LGSS_SVC_OSS_STR;
338 if (lgss_krb5_strcmp(krb5_princ_name(ctx, princ), princ_name)) {
339 logmsg(LL_WARN, "%.*s: we expect %s instead\n",
340 krb5_princ_name(ctx, princ)->length,
341 krb5_princ_name(ctx, princ)->data,
347 * verify the hostname part of the principal, except we do allow
348 * lustre_root without binding to a host.
350 if (krb5_princ_component(ctx, princ, 1) == NULL) {
351 if (flag != LGSS_ROOT_CRED_ROOT) {
352 logmsg(LL_WARN, "%.*s: missing hostname\n",
353 krb5_princ_name(ctx, princ)->length,
354 krb5_princ_name(ctx, princ)->data);
358 if (svc_princ_verify_host(ctx, princ, self_nid, LL_WARN)) {
359 logmsg(LL_DEBUG, "%.*s: doesn't belong to this node\n",
360 krb5_princ_name(ctx, princ)->length,
361 krb5_princ_name(ctx, princ)->data);
366 logmsg(LL_TRACE, "principal is OK\n");
371 * compose the TGT cc name, according to the root flags.
374 void get_root_tgt_ccname(char *ccname, int size, unsigned int flag)
379 case LGSS_ROOT_CRED_ROOT:
380 suffix = krb5_cred_root_suffix;
382 case LGSS_ROOT_CRED_MDT:
383 suffix = krb5_cred_mds_suffix;
385 case LGSS_ROOT_CRED_OST:
386 suffix = krb5_cred_oss_suffix;
392 snprintf(ccname, size, "%s%s/%s%s_%s",
393 krb5_cc_type, krb5_cc_dir, krb5_cred_prefix,
394 suffix, krb5_this_realm);
398 int lkrb5_check_root_tgt_cc_base(krb5_context ctx,
404 krb5_ccache tgt_ccache;
406 krb5_principal princ;
407 krb5_cc_cursor cursor;
408 krb5_error_code code;
410 int rc = -1, found = 0;
412 /* prepare parsing the cache file */
413 code = krb5_cc_resolve(ctx, ccname, &tgt_ccache);
415 logmsg(LL_ERR, "resolve krb5 cc %s: %s\n",
416 ccname, krb5_err_msg(code));
420 /* checks the principal */
421 code = krb5_cc_get_principal(ctx, tgt_ccache, &princ);
423 logmsg(LL_ERR, "get cc principal: %s\n", krb5_err_msg(code));
427 if (lkrb5_cc_check_tgt_princ(ctx, tgt_ccache, princ, flag, self_nid))
433 code = krb5_cc_start_seq_get(ctx, tgt_ccache, &cursor);
435 logmsg(LL_ERR, "start cc iteration: %s\n", krb5_err_msg(code));
441 krb5_timestamp duration, delta;
443 code = krb5_cc_next_cred(ctx, tgt_ccache, &cursor, &cred);
447 logmsg(LL_DEBUG, "cred: server realm %.*s, type %d, name %.*s; "
448 "time (%lld-%lld, renew till %lld), valid %lld\n",
449 krb5_princ_realm(ctx, cred.server)->length,
450 krb5_princ_realm(ctx, cred.server)->data,
451 krb5_princ_type(ctx, cred.server),
452 krb5_princ_name(ctx, cred.server)->length,
453 krb5_princ_name(ctx, cred.server)->data,
454 (long long)cred.times.starttime,
455 (long long)cred.times.endtime,
456 (long long)cred.times.renew_till,
457 (long long)(cred.times.endtime - now));
460 * we found the princ type is always 0 (KRB5_NT_UNKNOWN), why???
463 /* FIXME how about inter-realm TGT??? FIXME */
464 if (lgss_krb5_strcasecmp(krb5_princ_name(ctx, cred.server),
468 if (lgss_krb5_strcasecmp(krb5_princ_realm(ctx, cred.server),
472 /* check validity of time */
473 delta = 60 * 30; /* half an hour */
474 duration = cred.times.endtime - cred.times.starttime;
475 if (duration / 4 < delta)
476 delta = duration / 4;
478 if (cred.times.starttime <= now &&
479 cred.times.endtime >= now + delta) {
486 logmsg(LL_DEBUG, "doesn't find good TGT cache\n");
490 /* found a good cred, store it into @ccache */
491 logmsg(LL_DEBUG, "found good TGT cache\n");
493 code = krb5_cc_initialize(ctx, ccache, princ);
495 logmsg(LL_ERR, "init private cc: %s\n", krb5_err_msg(code));
499 code = krb5_cc_store_cred(ctx, ccache, &cred);
501 logmsg(LL_ERR, "store private cred: %s\n", krb5_err_msg(code));
505 logmsg(LL_DEBUG, "store private ccache OK\n");
509 krb5_cc_end_seq_get(ctx, tgt_ccache, &cursor);
511 krb5_free_principal(ctx, princ);
513 krb5_cc_close(ctx, tgt_ccache);
519 * find out whether current TGT cache is valid or not
522 int lkrb5_check_root_tgt_cc(krb5_context ctx,
524 unsigned int root_flags,
533 for (i = 0; i < LGSS_ROOT_CRED_NR; i++) {
536 if ((root_flags & flag) == 0)
539 get_root_tgt_ccname(ccname, sizeof(ccname), flag);
540 logmsg(LL_DEBUG, "root krb5 TGT ccname: %s\n", ccname);
542 /* currently we only support type "FILE", firstly make sure
543 * the cache file is there */
544 ccfile = ccname + strlen(krb5_cc_type);
545 if (stat(ccfile, &statbuf)) {
546 logmsg(LL_DEBUG, "krb5 cc %s: %s\n",
547 ccname, strerror(errno));
551 rc = lkrb5_check_root_tgt_cc_base(ctx, ccache, ccname, flag,
557 logmsg(LL_TRACE, "doesn't find a valid tgt cc\n");
562 int lkrb5_get_root_tgt_keytab(krb5_context ctx,
565 krb5_principal princ,
568 krb5_get_init_creds_opt opts;
570 krb5_ccache tgt_ccache;
571 krb5_error_code code;
574 krb5_get_init_creds_opt_init(&opts);
575 krb5_get_init_creds_opt_set_address_list(&opts, NULL);
577 * by default krb5 library obtain ticket with lifetime shorter
578 * than the max value. we can change it here if we want. but
579 * seems not necessary now.
581 krb5_get_init_creds_opt_set_tkt_life(&opts, very-long-time);
586 * obtain TGT and store into global ccache
588 code = krb5_get_init_creds_keytab(ctx, &cred, princ, kt,
591 logmsg(LL_ERR, "failed to get root TGT for "
592 "principal %.*s: %s\n",
593 krb5_princ_name(ctx, princ)->length,
594 krb5_princ_name(ctx, princ)->data,
599 code = krb5_cc_resolve(ctx, ccname, &tgt_ccache);
601 logmsg(LL_ERR, "resolve cc %s: %s\n",
602 ccname, krb5_err_msg(code));
606 code = krb5_cc_initialize(ctx, tgt_ccache, princ);
608 logmsg(LL_ERR, "initialize cc %s: %s\n",
609 ccname, krb5_err_msg(code));
613 code = krb5_cc_store_cred(ctx, tgt_ccache, &cred);
615 logmsg(LL_ERR, "store cred to cc %s: %s\n",
616 ccname, krb5_err_msg(code));
620 logmsg(LL_INFO, "installed TGT of %.*s in cc %s\n",
621 krb5_princ_name(ctx, princ)->length,
622 krb5_princ_name(ctx, princ)->data,
626 * now store the cred into my own cc too
628 code = krb5_cc_initialize(ctx, ccache, princ);
630 logmsg(LL_ERR, "init mem cc: %s\n", krb5_err_msg(code));
634 code = krb5_cc_store_cred(ctx, ccache, &cred);
636 logmsg(LL_ERR, "store mm cred: %s\n", krb5_err_msg(code));
640 logmsg(LL_DEBUG, "stored TGT into mem cc OK\n");
643 krb5_cc_close(ctx, tgt_ccache);
645 krb5_free_cred_contents(ctx, &cred);
650 * obtain a new root TGT
653 int lkrb5_refresh_root_tgt_cc(krb5_context ctx,
655 unsigned int root_flags,
659 krb5_keytab_entry kte;
660 krb5_kt_cursor cursor;
661 krb5_principal princ = NULL;
662 krb5_error_code code;
664 unsigned int flag = 0;
667 /* prepare parsing the keytab file */
668 code = krb5_kt_resolve(ctx, krb5_keytab_file, &kt);
670 logmsg(LL_ERR, "resolve keytab %s: %s\n",
671 krb5_keytab_file, krb5_err_msg(code));
675 code = krb5_kt_start_seq_get(ctx, kt, &cursor);
677 logmsg(LL_ERR, "start kt iteration: %s\n", krb5_err_msg(code));
681 /* iterate keytab to find proper an entry */
683 krb5_data *princname;
685 code = krb5_kt_next_entry(ctx, kt, &kte, &cursor);
689 logmsg(LL_TRACE, "kt entry: realm %.*s, type %d, "
690 "size %d, name %.*s\n",
691 krb5_princ_realm(ctx, kte.principal)->length,
692 krb5_princ_realm(ctx, kte.principal)->data,
693 krb5_princ_type(ctx, kte.principal),
694 krb5_princ_size(ctx, kte.principal),
695 krb5_princ_name(ctx, kte.principal)->length,
696 krb5_princ_name(ctx, kte.principal)->data);
698 if (!princ_is_local_realm(ctx, kte.principal))
701 princname = krb5_princ_name(ctx, kte.principal);
703 if ((root_flags & LGSS_ROOT_CRED_ROOT) != 0 &&
704 lgss_krb5_strcmp(princname, LGSS_USR_ROOT_STR) == 0) {
705 flag = LGSS_ROOT_CRED_ROOT;
706 } else if ((root_flags & LGSS_ROOT_CRED_MDT) != 0 &&
707 lgss_krb5_strcmp(princname, LGSS_SVC_MDS_STR) == 0) {
708 flag = LGSS_ROOT_CRED_MDT;
709 } else if ((root_flags & LGSS_ROOT_CRED_OST) != 0 &&
710 lgss_krb5_strcmp(princname, LGSS_SVC_OSS_STR) == 0) {
711 flag = LGSS_ROOT_CRED_OST;
713 logmsg(LL_TRACE, "not what we want, skip\n");
717 if (krb5_princ_component(ctx, kte.principal, 1) == NULL) {
718 if (flag != LGSS_ROOT_CRED_ROOT) {
719 logmsg(LL_TRACE, "no hostname, skip\n");
723 if (svc_princ_verify_host(ctx, kte.principal, self_nid,
725 logmsg(LL_TRACE, "doesn't belong to this "
731 code = krb5_copy_principal(ctx, kte.principal, &princ);
733 logmsg(LL_ERR, "copy princ: %s\n", krb5_err_msg(code));
737 lassert(princ != NULL);
741 krb5_kt_end_seq_get(ctx, kt, &cursor);
744 logmsg(LL_ERR, "can't find proper keytab entry\n");
748 /* obtain root TGT */
749 get_root_tgt_ccname(ccname, sizeof(ccname), flag);
750 rc = lkrb5_get_root_tgt_keytab(ctx, ccache, kt, princ, ccname);
752 krb5_free_principal(ctx, princ);
754 krb5_kt_close(ctx, kt);
759 int lkrb5_prepare_root_cred(struct lgss_cred *cred)
763 krb5_error_code code;
764 struct lgss_krb5_cred *kcred;
767 lassert(krb5_this_realm != NULL);
769 kcred = (struct lgss_krb5_cred *) cred->lc_mech_cred;
771 /* compose the memory cc name, since the only user is myself,
772 * the name could be fixed */
773 snprintf(kcred->kc_ccname, sizeof(kcred->kc_ccname),
774 "%s/self", krb5_cc_type_mem);
775 logmsg(LL_TRACE, "private cc: %s\n", kcred->kc_ccname);
777 code = krb5_init_context(&ctx);
779 logmsg(LL_ERR, "initialize krb5 context: %s\n",
784 code = krb5_cc_resolve(ctx, kcred->kc_ccname, &ccache);
786 logmsg(LL_ERR, "resolve krb5 cc %s: %s\n",
787 kcred->kc_ccname, krb5_err_msg(code));
792 * search and/or obtain root TGT credential.
793 * it touched global (on-disk) tgt cache, do it inside mutex locking
795 lgss_krb5_mutex_lock();
797 rc = lkrb5_check_root_tgt_cc(ctx, ccache, cred->lc_root_flags,
800 rc = lkrb5_refresh_root_tgt_cc(ctx, ccache,
805 rc = lgss_krb5_set_ccache_name(kcred->kc_ccname);
807 lgss_krb5_mutex_unlock();
809 krb5_cc_close(ctx, ccache);
811 krb5_free_context(ctx);
813 logmsg(LL_DEBUG, "prepare root credentail %s\n", rc ? "failed" : "OK");
818 int lkrb5_prepare_user_cred(struct lgss_cred *cred)
820 struct lgss_krb5_cred *kcred;
823 lassert(krb5_this_realm == NULL);
825 kcred = (struct lgss_krb5_cred *) cred->lc_mech_cred;
828 * here we just specified a fix ccname, instead of searching
829 * entire cc dir. is this OK??
831 snprintf(kcred->kc_ccname, sizeof(kcred->kc_ccname),
833 krb5_cc_type, krb5_cc_dir, krb5_cred_prefix, cred->lc_uid);
834 logmsg(LL_DEBUG, "using krb5 cache name: %s\n", kcred->kc_ccname);
836 rc = lgss_krb5_set_ccache_name(kcred->kc_ccname);
838 logmsg(LL_ERR, "can't set krb5 ccache name: %s\n",
845 int lgss_krb5_prepare_cred(struct lgss_cred *cred)
847 struct lgss_krb5_cred *kcred;
850 kcred = malloc(sizeof(*kcred));
852 logmsg(LL_ERR, "can't allocate krb5 cred\n");
856 kcred->kc_ccname[0] = '\0';
857 kcred->kc_remove = 0;
858 cred->lc_mech_cred = kcred;
860 if (cred->lc_root_flags != 0) {
861 if (lgss_krb5_get_local_realm())
864 rc = lkrb5_prepare_root_cred(cred);
866 rc = lkrb5_prepare_user_cred(cred);
873 void lgss_krb5_release_cred(struct lgss_cred *cred)
875 struct lgss_krb5_cred *kcred;
877 kcred = (struct lgss_krb5_cred *) cred->lc_mech_cred;
880 cred->lc_mech_cred = NULL;
883 struct lgss_mech_type lgss_mech_krb5 =
886 .lmt_mech_n = LGSS_MECH_KRB5,
887 .lmt_prepare_cred = lgss_krb5_prepare_cred,
888 .lmt_release_cred = lgss_krb5_release_cred,