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>
123 #include <gssapi/gssapi.h>
124 #ifdef USE_PRIVATE_KRB5_FUNCTIONS
125 #include <gssapi/gssapi_krb5.h>
129 #include "lsupport.h"
130 #include "lgss_utils.h"
131 #include "lgss_krb5_utils.h"
133 static void lgss_krb5_mutex_lock(void)
135 if (lgss_mutex_lock(LGSS_MUTEX_KRB5)) {
136 logmsg(LL_ERR, "can't lock process, abort!\n");
141 static void lgss_krb5_mutex_unlock(void)
143 if (lgss_mutex_unlock(LGSS_MUTEX_KRB5)) {
144 logmsg(LL_WARN, "can't unlock process, other processes "
145 "might need to wait long time\n");
151 * - currently we only support "normal" cache types: "FILE" and "MEMORY".
154 #define krb5_err_msg(code) error_message(code)
156 const char *krb5_cc_type_mem = "MEMORY:";
157 const char *krb5_cc_type_file = "FILE:";
158 const char *krb5_cred_root_suffix = "lustre_root";
159 const char *krb5_cred_mds_suffix = "lustre_mds";
160 const char *krb5_cred_oss_suffix = "lustre_oss";
162 char *krb5_this_realm = NULL;
163 char *krb5_keytab_file = "/etc/krb5.keytab";
164 char *krb5_cc_type = "FILE:";
165 char *krb5_cc_dir = "/tmp";
166 char *krb5_cred_prefix = "krb5cc_";
168 struct lgss_krb5_cred {
170 int kc_remove; /* remove cache upon release */
174 int lgss_krb5_set_ccache_name(const char *ccname)
176 #ifdef USE_GSS_KRB5_CCACHE_NAME
177 unsigned int maj_stat, min_stat;
179 maj_stat = gss_krb5_ccache_name(&min_stat, ccname, NULL);
180 if (maj_stat != GSS_S_COMPLETE) {
181 logmsg(LL_ERR, "failed to set ccache name\n");
186 * Set the KRB5CCNAME environment variable to tell the krb5 code
187 * which credentials cache to use. (Instead of using the private
188 * function above for which there is no generic gssapi equivalent)
190 if (setenv("KRB5CCNAME", ccname, 1)) {
191 logmsg(LL_ERR, "set env of krb5 ccname: %s\n",
196 logmsg(LL_DEBUG, "set cc: %s\n", ccname);
201 int lgss_krb5_get_local_realm(void)
203 krb5_context context = NULL;
204 krb5_error_code code;
207 if (krb5_this_realm != NULL)
210 code = krb5_init_context(&context);
212 logmsg(LL_ERR, "init ctx: %s\n", krb5_err_msg(code));
216 code = krb5_get_default_realm(context, &krb5_this_realm);
218 logmsg(LL_ERR, "get default realm: %s\n", krb5_err_msg(code));
222 logmsg(LL_DEBUG, "Local realm: %s\n", krb5_this_realm);
225 krb5_free_context(context);
230 int princ_is_local_realm(krb5_context ctx, krb5_principal princ)
232 return (lgss_krb5_strcasecmp(krb5_princ_realm(ctx, princ),
233 krb5_this_realm) == 0);
237 int svc_princ_verify_host(krb5_context ctx,
238 krb5_principal princ,
242 struct utsname utsbuf;
243 struct hostent *host;
244 const int max_namelen = 512;
245 char namebuf[max_namelen];
248 if (krb5_princ_component(ctx, princ, 1) == NULL) {
249 logmsg(loglevel, "service principal has no host part\n");
254 if (lnet_nid2hostname(self_nid, namebuf, max_namelen)) {
256 "can't resolve hostname from nid %llx\n",
262 if (uname(&utsbuf)) {
263 logmsg(loglevel, "get UTS name: %s\n", strerror(errno));
267 host = gethostbyname(utsbuf.nodename);
269 logmsg(loglevel, "failed to get local hostname\n");
272 h_name = host->h_name;
275 if (lgss_krb5_strcasecmp(krb5_princ_component(ctx, princ, 1),
277 logmsg(loglevel, "service principal: hostname %.*s "
278 "doesn't match localhost %s\n",
279 krb5_princ_component(ctx, princ, 1)->length,
280 krb5_princ_component(ctx, princ, 1)->data,
289 int lkrb5_cc_check_tgt_princ(krb5_context ctx,
291 krb5_principal princ,
295 const char *princ_name;
297 logmsg(LL_DEBUG, "principal: realm %.*s, type %d, size %d, name %.*s\n",
298 krb5_princ_realm(ctx, princ)->length,
299 krb5_princ_realm(ctx, princ)->data,
300 krb5_princ_type(ctx, princ),
301 krb5_princ_size(ctx, princ),
302 krb5_princ_name(ctx, princ)->length,
303 krb5_princ_name(ctx, princ)->data);
306 if (krb5_princ_type(ctx, princ) != KRB5_NT_PRINCIPAL) {
307 logmsg(LL_WARN, "principal type %d is not I want\n",
308 krb5_princ_type(ctx, princ));
312 /* check local realm */
313 if (!princ_is_local_realm(ctx, princ)) {
314 logmsg(LL_WARN, "principal realm %.*s not local: %s\n",
315 krb5_princ_realm(ctx, princ)->length,
316 krb5_princ_realm(ctx, princ)->data,
321 /* check principal name */
323 case LGSS_ROOT_CRED_ROOT:
324 princ_name = LGSS_USR_ROOT_STR;
326 case LGSS_ROOT_CRED_MDT:
327 princ_name = LGSS_SVC_MDS_STR;
329 case LGSS_ROOT_CRED_OST:
330 princ_name = LGSS_SVC_OSS_STR;
336 if (lgss_krb5_strcmp(krb5_princ_name(ctx, princ), princ_name)) {
337 logmsg(LL_WARN, "%.*s: we expect %s instead\n",
338 krb5_princ_name(ctx, princ)->length,
339 krb5_princ_name(ctx, princ)->data,
345 * verify the hostname part of the principal, except we do allow
346 * lustre_root without binding to a host.
348 if (krb5_princ_component(ctx, princ, 1) == NULL) {
349 if (flag != LGSS_ROOT_CRED_ROOT) {
350 logmsg(LL_WARN, "%.*s: missing hostname\n",
351 krb5_princ_name(ctx, princ)->length,
352 krb5_princ_name(ctx, princ)->data);
356 if (svc_princ_verify_host(ctx, princ, self_nid, LL_WARN)) {
357 logmsg(LL_DEBUG, "%.*s: doesn't belong to this node\n",
358 krb5_princ_name(ctx, princ)->length,
359 krb5_princ_name(ctx, princ)->data);
364 logmsg(LL_TRACE, "principal is OK\n");
369 * compose the TGT cc name, according to the root flags.
372 void get_root_tgt_ccname(char *ccname, int size, unsigned int flag)
377 case LGSS_ROOT_CRED_ROOT:
378 suffix = krb5_cred_root_suffix;
380 case LGSS_ROOT_CRED_MDT:
381 suffix = krb5_cred_mds_suffix;
383 case LGSS_ROOT_CRED_OST:
384 suffix = krb5_cred_oss_suffix;
390 snprintf(ccname, size, "%s%s/%s%s_%s",
391 krb5_cc_type, krb5_cc_dir, krb5_cred_prefix,
392 suffix, krb5_this_realm);
396 int lkrb5_check_root_tgt_cc_base(krb5_context ctx,
402 krb5_ccache tgt_ccache;
404 krb5_principal princ;
405 krb5_cc_cursor cursor;
406 krb5_error_code code;
408 int rc = -1, found = 0;
410 /* prepare parsing the cache file */
411 code = krb5_cc_resolve(ctx, ccname, &tgt_ccache);
413 logmsg(LL_ERR, "resolve krb5 cc %s: %s\n",
414 ccname, krb5_err_msg(code));
418 /* checks the principal */
419 code = krb5_cc_get_principal(ctx, tgt_ccache, &princ);
421 logmsg(LL_ERR, "get cc principal: %s\n", krb5_err_msg(code));
425 if (lkrb5_cc_check_tgt_princ(ctx, tgt_ccache, princ, flag, self_nid))
431 code = krb5_cc_start_seq_get(ctx, tgt_ccache, &cursor);
433 logmsg(LL_ERR, "start cc iteration: %s\n", krb5_err_msg(code));
439 krb5_timestamp duration, delta;
441 code = krb5_cc_next_cred(ctx, tgt_ccache, &cursor, &cred);
445 logmsg(LL_DEBUG, "cred: server realm %.*s, type %d, name %.*s; "
446 "time (%d-%d, renew till %d), valid %d\n",
447 krb5_princ_realm(ctx, cred.server)->length,
448 krb5_princ_realm(ctx, cred.server)->data,
449 krb5_princ_type(ctx, cred.server),
450 krb5_princ_name(ctx, cred.server)->length,
451 krb5_princ_name(ctx, cred.server)->data,
452 cred.times.starttime, cred.times.endtime,
453 cred.times.renew_till, cred.times.endtime - now);
456 * we found the princ type is always 0 (KRB5_NT_UNKNOWN), why???
459 /* FIXME how about inter-realm TGT??? FIXME */
460 if (lgss_krb5_strcasecmp(krb5_princ_name(ctx, cred.server),
464 if (lgss_krb5_strcasecmp(krb5_princ_realm(ctx, cred.server),
468 /* check validity of time */
469 delta = 60 * 30; /* half an hour */
470 duration = cred.times.endtime - cred.times.starttime;
471 if (duration / 4 < delta)
472 delta = duration / 4;
474 if (cred.times.starttime <= now &&
475 cred.times.endtime >= now + delta) {
482 logmsg(LL_DEBUG, "doesn't find good TGT cache\n");
486 /* found a good cred, store it into @ccache */
487 logmsg(LL_DEBUG, "found good TGT cache\n");
489 code = krb5_cc_initialize(ctx, ccache, princ);
491 logmsg(LL_ERR, "init private cc: %s\n", krb5_err_msg(code));
495 code = krb5_cc_store_cred(ctx, ccache, &cred);
497 logmsg(LL_ERR, "store private cred: %s\n", krb5_err_msg(code));
501 logmsg(LL_DEBUG, "store private ccache OK\n");
505 krb5_cc_end_seq_get(ctx, tgt_ccache, &cursor);
507 krb5_free_principal(ctx, princ);
509 krb5_cc_close(ctx, tgt_ccache);
515 * find out whether current TGT cache is valid or not
518 int lkrb5_check_root_tgt_cc(krb5_context ctx,
520 unsigned int root_flags,
529 for (i = 0; i < LGSS_ROOT_CRED_NR; i++) {
532 if ((root_flags & flag) == 0)
535 get_root_tgt_ccname(ccname, sizeof(ccname), flag);
536 logmsg(LL_DEBUG, "root krb5 TGT ccname: %s\n", ccname);
538 /* currently we only support type "FILE", firstly make sure
539 * the cache file is there */
540 ccfile = ccname + strlen(krb5_cc_type);
541 if (stat(ccfile, &statbuf)) {
542 logmsg(LL_DEBUG, "krb5 cc %s: %s\n",
543 ccname, strerror(errno));
547 rc = lkrb5_check_root_tgt_cc_base(ctx, ccache, ccname, flag,
553 logmsg(LL_TRACE, "doesn't find a valid tgt cc\n");
558 int lkrb5_get_root_tgt_keytab(krb5_context ctx,
561 krb5_principal princ,
564 krb5_get_init_creds_opt opts;
566 krb5_ccache tgt_ccache;
567 krb5_error_code code;
570 krb5_get_init_creds_opt_init(&opts);
571 krb5_get_init_creds_opt_set_address_list(&opts, NULL);
573 * by default krb5 library obtain ticket with lifetime shorter
574 * than the max value. we can change it here if we want. but
575 * seems not necessary now.
577 krb5_get_init_creds_opt_set_tkt_life(&opts, very-long-time);
582 * obtain TGT and store into global ccache
584 code = krb5_get_init_creds_keytab(ctx, &cred, princ, kt,
587 logmsg(LL_ERR, "failed to get root TGT for "
588 "principal %.*s: %s\n",
589 krb5_princ_name(ctx, princ)->length,
590 krb5_princ_name(ctx, princ)->data,
595 code = krb5_cc_resolve(ctx, ccname, &tgt_ccache);
597 logmsg(LL_ERR, "resolve cc %s: %s\n",
598 ccname, krb5_err_msg(code));
602 code = krb5_cc_initialize(ctx, tgt_ccache, princ);
604 logmsg(LL_ERR, "initialize cc %s: %s\n",
605 ccname, krb5_err_msg(code));
609 code = krb5_cc_store_cred(ctx, tgt_ccache, &cred);
611 logmsg(LL_ERR, "store cred to cc %s: %s\n",
612 ccname, krb5_err_msg(code));
616 logmsg(LL_INFO, "installed TGT of %.*s in cc %s\n",
617 krb5_princ_name(ctx, princ)->length,
618 krb5_princ_name(ctx, princ)->data,
622 * now store the cred into my own cc too
624 code = krb5_cc_initialize(ctx, ccache, princ);
626 logmsg(LL_ERR, "init mem cc: %s\n", krb5_err_msg(code));
630 code = krb5_cc_store_cred(ctx, ccache, &cred);
632 logmsg(LL_ERR, "store mm cred: %s\n", krb5_err_msg(code));
636 logmsg(LL_DEBUG, "stored TGT into mem cc OK\n");
639 krb5_cc_close(ctx, tgt_ccache);
641 krb5_free_cred_contents(ctx, &cred);
646 * obtain a new root TGT
649 int lkrb5_refresh_root_tgt_cc(krb5_context ctx,
651 unsigned int root_flags,
655 krb5_keytab_entry kte;
656 krb5_kt_cursor cursor;
657 krb5_principal princ = NULL;
658 krb5_error_code code;
660 unsigned int flag = 0;
663 /* prepare parsing the keytab file */
664 code = krb5_kt_resolve(ctx, krb5_keytab_file, &kt);
666 logmsg(LL_ERR, "resolve keytab %s: %s\n",
667 krb5_keytab_file, krb5_err_msg(code));
671 code = krb5_kt_start_seq_get(ctx, kt, &cursor);
673 logmsg(LL_ERR, "start kt iteration: %s\n", krb5_err_msg(code));
677 /* iterate keytab to find proper an entry */
679 krb5_data *princname;
681 code = krb5_kt_next_entry(ctx, kt, &kte, &cursor);
685 logmsg(LL_TRACE, "kt entry: realm %.*s, type %d, "
686 "size %d, name %.*s\n",
687 krb5_princ_realm(ctx, kte.principal)->length,
688 krb5_princ_realm(ctx, kte.principal)->data,
689 krb5_princ_type(ctx, kte.principal),
690 krb5_princ_size(ctx, kte.principal),
691 krb5_princ_name(ctx, kte.principal)->length,
692 krb5_princ_name(ctx, kte.principal)->data);
694 if (!princ_is_local_realm(ctx, kte.principal))
697 princname = krb5_princ_name(ctx, kte.principal);
699 if ((root_flags & LGSS_ROOT_CRED_ROOT) != 0 &&
700 lgss_krb5_strcmp(princname, LGSS_USR_ROOT_STR) == 0) {
701 flag = LGSS_ROOT_CRED_ROOT;
702 } else if ((root_flags & LGSS_ROOT_CRED_MDT) != 0 &&
703 lgss_krb5_strcmp(princname, LGSS_SVC_MDS_STR) == 0) {
704 flag = LGSS_ROOT_CRED_MDT;
705 } else if ((root_flags & LGSS_ROOT_CRED_OST) != 0 &&
706 lgss_krb5_strcmp(princname, LGSS_SVC_OSS_STR) == 0) {
707 flag = LGSS_ROOT_CRED_OST;
709 logmsg(LL_TRACE, "not what we want, skip\n");
713 if (krb5_princ_component(ctx, kte.principal, 1) == NULL) {
714 if (flag != LGSS_ROOT_CRED_ROOT) {
715 logmsg(LL_TRACE, "no hostname, skip\n");
719 if (svc_princ_verify_host(ctx, kte.principal, self_nid,
721 logmsg(LL_TRACE, "doesn't belong to this "
727 code = krb5_copy_principal(ctx, kte.principal, &princ);
729 logmsg(LL_ERR, "copy princ: %s\n", krb5_err_msg(code));
733 lassert(princ != NULL);
737 krb5_kt_end_seq_get(ctx, kt, &cursor);
740 logmsg(LL_ERR, "can't find proper keytab entry\n");
744 /* obtain root TGT */
745 get_root_tgt_ccname(ccname, sizeof(ccname), flag);
746 rc = lkrb5_get_root_tgt_keytab(ctx, ccache, kt, princ, ccname);
748 krb5_free_principal(ctx, princ);
750 krb5_kt_close(ctx, kt);
755 int lkrb5_prepare_root_cred(struct lgss_cred *cred)
759 krb5_error_code code;
760 struct lgss_krb5_cred *kcred;
763 lassert(krb5_this_realm != NULL);
765 kcred = (struct lgss_krb5_cred *) cred->lc_mech_cred;
767 /* compose the memory cc name, since the only user is myself,
768 * the name could be fixed */
769 snprintf(kcred->kc_ccname, sizeof(kcred->kc_ccname),
770 "%s/self", krb5_cc_type_mem);
771 logmsg(LL_TRACE, "private cc: %s\n", kcred->kc_ccname);
773 code = krb5_init_context(&ctx);
775 logmsg(LL_ERR, "initialize krb5 context: %s\n",
780 code = krb5_cc_resolve(ctx, kcred->kc_ccname, &ccache);
782 logmsg(LL_ERR, "resolve krb5 cc %s: %s\n",
783 kcred->kc_ccname, krb5_err_msg(code));
788 * search and/or obtain root TGT credential.
789 * it touched global (on-disk) tgt cache, do it inside mutex locking
791 lgss_krb5_mutex_lock();
793 rc = lkrb5_check_root_tgt_cc(ctx, ccache, cred->lc_root_flags,
796 rc = lkrb5_refresh_root_tgt_cc(ctx, ccache,
801 rc = lgss_krb5_set_ccache_name(kcred->kc_ccname);
803 lgss_krb5_mutex_unlock();
805 krb5_cc_close(ctx, ccache);
807 krb5_free_context(ctx);
809 logmsg(LL_DEBUG, "prepare root credentail %s\n", rc ? "failed" : "OK");
814 int lkrb5_prepare_user_cred(struct lgss_cred *cred)
816 struct lgss_krb5_cred *kcred;
819 lassert(krb5_this_realm == NULL);
821 kcred = (struct lgss_krb5_cred *) cred->lc_mech_cred;
824 * here we just specified a fix ccname, instead of searching
825 * entire cc dir. is this OK??
827 snprintf(kcred->kc_ccname, sizeof(kcred->kc_ccname),
829 krb5_cc_type, krb5_cc_dir, krb5_cred_prefix, cred->lc_uid);
830 logmsg(LL_DEBUG, "using krb5 cache name: %s\n", kcred->kc_ccname);
832 rc = lgss_krb5_set_ccache_name(kcred->kc_ccname);
834 logmsg(LL_ERR, "can't set krb5 ccache name: %s\n",
841 int lgss_krb5_prepare_cred(struct lgss_cred *cred)
843 struct lgss_krb5_cred *kcred;
846 kcred = malloc(sizeof(*kcred));
848 logmsg(LL_ERR, "can't allocate krb5 cred\n");
852 kcred->kc_ccname[0] = '\0';
853 kcred->kc_remove = 0;
854 cred->lc_mech_cred = kcred;
856 if (cred->lc_root_flags != 0) {
857 if (lgss_krb5_get_local_realm())
860 rc = lkrb5_prepare_root_cred(cred);
862 rc = lkrb5_prepare_user_cred(cred);
869 void lgss_krb5_release_cred(struct lgss_cred *cred)
871 struct lgss_krb5_cred *kcred;
873 kcred = (struct lgss_krb5_cred *) cred->lc_mech_cred;
876 cred->lc_mech_cred = NULL;
879 struct lgss_mech_type lgss_mech_krb5 =
882 .lmt_mech_n = LGSS_MECH_KRB5,
883 .lmt_prepare_cred = lgss_krb5_prepare_cred,
884 .lmt_release_cred = lgss_krb5_release_cred,