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 "lgss_utils.h"
130 #include "lgss_krb5_utils.h"
132 static void lgss_krb5_mutex_lock(void)
134 if (lgss_mutex_lock(LGSS_MUTEX_KRB5)) {
135 logmsg(LL_ERR, "can't lock process, abort!\n");
140 static void lgss_krb5_mutex_unlock(void)
142 if (lgss_mutex_unlock(LGSS_MUTEX_KRB5)) {
143 logmsg(LL_WARN, "can't unlock process, other processes "
144 "might need to wait long time\n");
150 * - currently we only support "normal" cache types: "FILE" and "MEMORY".
153 #define krb5_err_msg(code) error_message(code)
155 const char *krb5_cc_type_mem = "MEMORY:";
156 const char *krb5_cc_type_file = "FILE:";
157 const char *krb5_cred_root_suffix = "lustre_root";
158 const char *krb5_cred_mds_suffix = "lustre_mds";
159 const char *krb5_cred_oss_suffix = "lustre_oss";
161 char *krb5_this_realm = NULL;
162 char *krb5_keytab_file = "/etc/krb5.keytab";
163 char *krb5_cc_type = "FILE:";
164 char *krb5_cc_dir = "/tmp";
165 char *krb5_cred_prefix = "krb5cc_";
167 struct lgss_krb5_cred {
169 int kc_remove; /* remove cache upon release */
173 int lgss_krb5_set_ccache_name(const char *ccname)
175 #ifdef USE_GSS_KRB5_CCACHE_NAME
176 unsigned int maj_stat, min_stat;
178 maj_stat = gss_krb5_ccache_name(&min_stat, ccname, NULL);
179 if (maj_stat != GSS_S_COMPLETE) {
180 logmsg(LL_ERR, "failed to set ccache name\n");
185 * Set the KRB5CCNAME environment variable to tell the krb5 code
186 * which credentials cache to use. (Instead of using the private
187 * function above for which there is no generic gssapi equivalent)
189 if (setenv("KRB5CCNAME", ccname, 1)) {
190 logmsg(LL_ERR, "set env of krb5 ccname: %s\n",
195 logmsg(LL_DEBUG, "set cc: %s\n", ccname);
200 int lgss_krb5_get_local_realm(void)
202 krb5_context context = NULL;
203 krb5_error_code code;
206 if (krb5_this_realm != NULL)
209 code = krb5_init_context(&context);
211 logmsg(LL_ERR, "init ctx: %s\n", krb5_err_msg(code));
215 code = krb5_get_default_realm(context, &krb5_this_realm);
217 logmsg(LL_ERR, "get default realm: %s\n", krb5_err_msg(code));
221 logmsg(LL_DEBUG, "Local realm: %s\n", krb5_this_realm);
224 krb5_free_context(context);
229 int princ_is_local_realm(krb5_context ctx, krb5_principal princ)
231 return (lgss_krb5_strcasecmp(krb5_princ_realm(ctx, princ),
232 krb5_this_realm) == 0);
236 int svc_princ_verify_host(krb5_context ctx,
237 krb5_principal princ,
240 struct utsname utsbuf;
241 struct hostent *host;
243 if (krb5_princ_component(ctx, princ, 1) == NULL) {
244 logmsg(loglevel, "service principal has no host part\n");
248 if (uname(&utsbuf)) {
249 logmsg(loglevel, "get UTS name: %s\n", strerror(errno));
253 host = gethostbyname(utsbuf.nodename);
255 logmsg(loglevel, "failed to get local hostname\n");
259 if (lgss_krb5_strcasecmp(krb5_princ_component(ctx, princ, 1),
261 logmsg(loglevel, "service principal: hostname %.*s "
262 "doesn't match localhost %s\n",
263 krb5_princ_component(ctx, princ, 1)->length,
264 krb5_princ_component(ctx, princ, 1)->data,
273 int lkrb5_cc_check_tgt_princ(krb5_context ctx,
275 krb5_principal princ,
278 const char *princ_name;
280 logmsg(LL_DEBUG, "principal: realm %.*s, type %d, size %d, name %.*s\n",
281 krb5_princ_realm(ctx, princ)->length,
282 krb5_princ_realm(ctx, princ)->data,
283 krb5_princ_type(ctx, princ),
284 krb5_princ_size(ctx, princ),
285 krb5_princ_name(ctx, princ)->length,
286 krb5_princ_name(ctx, princ)->data);
289 if (krb5_princ_type(ctx, princ) != KRB5_NT_PRINCIPAL) {
290 logmsg(LL_WARN, "principal type %d is not I want\n",
291 krb5_princ_type(ctx, princ));
295 /* check local realm */
296 if (!princ_is_local_realm(ctx, princ)) {
297 logmsg(LL_WARN, "principal realm %.*s not local: %s\n",
298 krb5_princ_realm(ctx, princ)->length,
299 krb5_princ_realm(ctx, princ)->data,
304 /* check principal name */
306 case LGSS_ROOT_CRED_ROOT:
307 princ_name = LGSS_USR_ROOT_STR;
309 case LGSS_ROOT_CRED_MDT:
310 princ_name = LGSS_SVC_MDS_STR;
312 case LGSS_ROOT_CRED_OST:
313 princ_name = LGSS_SVC_OSS_STR;
319 if (lgss_krb5_strcmp(krb5_princ_name(ctx, princ), princ_name)) {
320 logmsg(LL_WARN, "%.*s: we expect %s instead\n",
321 krb5_princ_name(ctx, princ)->length,
322 krb5_princ_name(ctx, princ)->data,
328 * verify the hostname part of the principal, except we do allow
329 * lustre_root without binding to a host.
331 if (krb5_princ_component(ctx, princ, 1) == NULL) {
332 if (flag != LGSS_ROOT_CRED_ROOT) {
333 logmsg(LL_WARN, "%.*s: missing hostname\n",
334 krb5_princ_name(ctx, princ)->length,
335 krb5_princ_name(ctx, princ)->data);
339 if (svc_princ_verify_host(ctx, princ, LL_WARN)) {
340 logmsg(LL_DEBUG, "%.*s: doesn't belong to this node\n",
341 krb5_princ_name(ctx, princ)->length,
342 krb5_princ_name(ctx, princ)->data);
347 logmsg(LL_TRACE, "principal is OK\n");
352 * compose the TGT cc name, according to the root flags.
355 void get_root_tgt_ccname(char *ccname, int size, unsigned int flag)
360 case LGSS_ROOT_CRED_ROOT:
361 suffix = krb5_cred_root_suffix;
363 case LGSS_ROOT_CRED_MDT:
364 suffix = krb5_cred_mds_suffix;
366 case LGSS_ROOT_CRED_OST:
367 suffix = krb5_cred_oss_suffix;
373 snprintf(ccname, size, "%s%s/%s%s_%s",
374 krb5_cc_type, krb5_cc_dir, krb5_cred_prefix,
375 suffix, krb5_this_realm);
379 int lkrb5_check_root_tgt_cc_base(krb5_context ctx,
384 krb5_ccache tgt_ccache;
386 krb5_principal princ;
387 krb5_cc_cursor cursor;
388 krb5_error_code code;
390 int rc = -1, found = 0;
392 /* prepare parsing the cache file */
393 code = krb5_cc_resolve(ctx, ccname, &tgt_ccache);
395 logmsg(LL_ERR, "resolve krb5 cc %s: %s\n",
396 ccname, krb5_err_msg(code));
400 /* checks the principal */
401 code = krb5_cc_get_principal(ctx, tgt_ccache, &princ);
403 logmsg(LL_ERR, "get cc principal: %s\n", krb5_err_msg(code));
407 if (lkrb5_cc_check_tgt_princ(ctx, tgt_ccache, princ, flag))
413 code = krb5_cc_start_seq_get(ctx, tgt_ccache, &cursor);
415 logmsg(LL_ERR, "start cc iteration: %s\n", krb5_err_msg(code));
421 krb5_timestamp duration, delta;
423 code = krb5_cc_next_cred(ctx, tgt_ccache, &cursor, &cred);
427 logmsg(LL_DEBUG, "cred: server realm %.*s, type %d, name %.*s; "
428 "time (%d-%d, renew till %d), valid %d\n",
429 krb5_princ_realm(ctx, cred.server)->length,
430 krb5_princ_realm(ctx, cred.server)->data,
431 krb5_princ_type(ctx, cred.server),
432 krb5_princ_name(ctx, cred.server)->length,
433 krb5_princ_name(ctx, cred.server)->data,
434 cred.times.starttime, cred.times.endtime,
435 cred.times.renew_till, cred.times.endtime - now);
438 * we found the princ type is always 0 (KRB5_NT_UNKNOWN), why???
441 /* FIXME how about inter-realm TGT??? FIXME */
442 if (lgss_krb5_strcasecmp(krb5_princ_name(ctx, cred.server),
446 if (lgss_krb5_strcasecmp(krb5_princ_realm(ctx, cred.server),
450 /* check validity of time */
451 delta = 60 * 30; /* half an hour */
452 duration = cred.times.endtime - cred.times.starttime;
453 if (duration / 4 < delta)
454 delta = duration / 4;
456 if (cred.times.starttime <= now &&
457 cred.times.endtime >= now + delta) {
464 logmsg(LL_DEBUG, "doesn't find good TGT cache\n");
468 /* found a good cred, store it into @ccache */
469 logmsg(LL_DEBUG, "found good TGT cache\n");
471 code = krb5_cc_initialize(ctx, ccache, princ);
473 logmsg(LL_ERR, "init private cc: %s\n", krb5_err_msg(code));
477 code = krb5_cc_store_cred(ctx, ccache, &cred);
479 logmsg(LL_ERR, "store private cred: %s\n", krb5_err_msg(code));
483 logmsg(LL_DEBUG, "store private ccache OK\n");
487 krb5_cc_end_seq_get(ctx, tgt_ccache, &cursor);
489 krb5_free_principal(ctx, princ);
491 krb5_cc_close(ctx, tgt_ccache);
497 * find out whether current TGT cache is valid or not
500 int lkrb5_check_root_tgt_cc(krb5_context ctx,
502 unsigned int root_flags)
510 for (i = 0; i < LGSS_ROOT_CRED_NR; i++) {
513 if ((root_flags & flag) == 0)
516 get_root_tgt_ccname(ccname, sizeof(ccname), flag);
517 logmsg(LL_DEBUG, "root krb5 TGT ccname: %s\n", ccname);
519 /* currently we only support type "FILE", firstly make sure
520 * the cache file is there */
521 ccfile = ccname + strlen(krb5_cc_type);
522 if (stat(ccfile, &statbuf)) {
523 logmsg(LL_DEBUG, "krb5 cc %s: %s\n",
524 ccname, strerror(errno));
528 rc = lkrb5_check_root_tgt_cc_base(ctx, ccache, ccname, flag);
533 logmsg(LL_TRACE, "doesn't find a valid tgt cc\n");
538 int lkrb5_get_root_tgt_keytab(krb5_context ctx,
541 krb5_principal princ,
544 krb5_get_init_creds_opt opts;
546 krb5_ccache tgt_ccache;
547 krb5_error_code code;
550 krb5_get_init_creds_opt_init(&opts);
551 krb5_get_init_creds_opt_set_address_list(&opts, NULL);
553 * by default krb5 library obtain ticket with lifetime shorter
554 * than the max value. we can change it here if we want. but
555 * seems not necessary now.
557 krb5_get_init_creds_opt_set_tkt_life(&opts, very-long-time);
562 * obtain TGT and store into global ccache
564 code = krb5_get_init_creds_keytab(ctx, &cred, princ, kt,
567 logmsg(LL_ERR, "failed to get root TGT for "
568 "principal %.*s: %s\n",
569 krb5_princ_name(ctx, princ)->length,
570 krb5_princ_name(ctx, princ)->data,
575 code = krb5_cc_resolve(ctx, ccname, &tgt_ccache);
577 logmsg(LL_ERR, "resolve cc %s: %s\n",
578 ccname, krb5_err_msg(code));
582 code = krb5_cc_initialize(ctx, tgt_ccache, princ);
584 logmsg(LL_ERR, "initialize cc %s: %s\n",
585 ccname, krb5_err_msg(code));
589 code = krb5_cc_store_cred(ctx, tgt_ccache, &cred);
591 logmsg(LL_ERR, "store cred to cc %s: %s\n",
592 ccname, krb5_err_msg(code));
596 logmsg(LL_INFO, "installed TGT of %.*s in cc %s\n",
597 krb5_princ_name(ctx, princ)->length,
598 krb5_princ_name(ctx, princ)->data,
602 * now store the cred into my own cc too
604 code = krb5_cc_initialize(ctx, ccache, princ);
606 logmsg(LL_ERR, "init mem cc: %s\n", krb5_err_msg(code));
610 code = krb5_cc_store_cred(ctx, ccache, &cred);
612 logmsg(LL_ERR, "store mm cred: %s\n", krb5_err_msg(code));
616 logmsg(LL_DEBUG, "stored TGT into mem cc OK\n");
619 krb5_cc_close(ctx, tgt_ccache);
621 krb5_free_cred_contents(ctx, &cred);
626 * obtain a new root TGT
629 int lkrb5_refresh_root_tgt_cc(krb5_context ctx,
631 unsigned int root_flags)
634 krb5_keytab_entry kte;
635 krb5_kt_cursor cursor;
636 krb5_principal princ = NULL;
637 krb5_error_code code;
639 unsigned int flag = 0;
642 /* prepare parsing the keytab file */
643 code = krb5_kt_resolve(ctx, krb5_keytab_file, &kt);
645 logmsg(LL_ERR, "resolve keytab %s: %s\n",
646 krb5_keytab_file, krb5_err_msg(code));
650 code = krb5_kt_start_seq_get(ctx, kt, &cursor);
652 logmsg(LL_ERR, "start kt iteration: %s\n", krb5_err_msg(code));
656 /* iterate keytab to find proper a entry */
658 krb5_data *princname;
660 code = krb5_kt_next_entry(ctx, kt, &kte, &cursor);
664 logmsg(LL_TRACE, "kt entry: realm %.*s, type %d, "
665 "size %d, name %.*s\n",
666 krb5_princ_realm(ctx, kte.principal)->length,
667 krb5_princ_realm(ctx, kte.principal)->data,
668 krb5_princ_type(ctx, kte.principal),
669 krb5_princ_size(ctx, kte.principal),
670 krb5_princ_name(ctx, kte.principal)->length,
671 krb5_princ_name(ctx, kte.principal)->data);
673 if (!princ_is_local_realm(ctx, kte.principal))
676 princname = krb5_princ_name(ctx, kte.principal);
678 if ((root_flags & LGSS_ROOT_CRED_ROOT) != 0 &&
679 lgss_krb5_strcmp(princname, LGSS_USR_ROOT_STR) == 0) {
680 flag = LGSS_ROOT_CRED_ROOT;
681 } else if ((root_flags & LGSS_ROOT_CRED_MDT) != 0 &&
682 lgss_krb5_strcmp(princname, LGSS_SVC_MDS_STR) == 0) {
683 flag = LGSS_ROOT_CRED_MDT;
684 } else if ((root_flags & LGSS_ROOT_CRED_OST) != 0 &&
685 lgss_krb5_strcmp(princname, LGSS_SVC_OSS_STR) == 0) {
686 flag = LGSS_ROOT_CRED_OST;
688 logmsg(LL_TRACE, "not what we want, skip\n");
692 if (krb5_princ_component(ctx, kte.principal, 1) == NULL) {
693 if (flag != LGSS_ROOT_CRED_ROOT) {
694 logmsg(LL_TRACE, "no hostname, skip\n");
698 if (svc_princ_verify_host(ctx, kte.principal,
700 logmsg(LL_TRACE, "doesn't belong to this "
706 code = krb5_copy_principal(ctx, kte.principal, &princ);
708 logmsg(LL_ERR, "copy princ: %s\n", krb5_err_msg(code));
712 lassert(princ != NULL);
716 krb5_kt_end_seq_get(ctx, kt, &cursor);
719 logmsg(LL_ERR, "can't find proper keytab entry\n");
723 /* obtain root TGT */
724 get_root_tgt_ccname(ccname, sizeof(ccname), flag);
725 rc = lkrb5_get_root_tgt_keytab(ctx, ccache, kt, princ, ccname);
727 krb5_free_principal(ctx, princ);
729 krb5_kt_close(ctx, kt);
734 int lkrb5_prepare_root_cred(struct lgss_cred *cred)
738 krb5_error_code code;
739 struct lgss_krb5_cred *kcred;
742 lassert(krb5_this_realm != NULL);
744 kcred = (struct lgss_krb5_cred *) cred->lc_mech_cred;
746 /* compose the memory cc name, since the only user is myself,
747 * the name could be fixed */
748 snprintf(kcred->kc_ccname, sizeof(kcred->kc_ccname),
749 "%s/self", krb5_cc_type_mem);
750 logmsg(LL_TRACE, "private cc: %s\n", kcred->kc_ccname);
752 code = krb5_init_context(&ctx);
754 logmsg(LL_ERR, "initialize krb5 context: %s\n",
759 code = krb5_cc_resolve(ctx, kcred->kc_ccname, &ccache);
761 logmsg(LL_ERR, "resolve krb5 cc %s: %s\n",
762 kcred->kc_ccname, krb5_err_msg(code));
767 * search and/or obtain root TGT credential.
768 * it touched global (on-disk) tgt cache, do it inside mutex locking
770 lgss_krb5_mutex_lock();
772 rc = lkrb5_check_root_tgt_cc(ctx, ccache, cred->lc_root_flags);
774 rc = lkrb5_refresh_root_tgt_cc(ctx, ccache,
775 cred->lc_root_flags);
778 rc = lgss_krb5_set_ccache_name(kcred->kc_ccname);
780 lgss_krb5_mutex_unlock();
782 krb5_cc_close(ctx, ccache);
784 krb5_free_context(ctx);
786 logmsg(LL_DEBUG, "prepare root credentail %s\n", rc ? "failed" : "OK");
791 int lkrb5_prepare_user_cred(struct lgss_cred *cred)
793 struct lgss_krb5_cred *kcred;
796 lassert(krb5_this_realm == NULL);
798 kcred = (struct lgss_krb5_cred *) cred->lc_mech_cred;
801 * here we just specified a fix ccname, instead of searching
802 * entire cc dir. is this OK??
804 snprintf(kcred->kc_ccname, sizeof(kcred->kc_ccname),
806 krb5_cc_type, krb5_cc_dir, krb5_cred_prefix, cred->lc_uid);
807 logmsg(LL_DEBUG, "using krb5 cache name: %s\n", kcred->kc_ccname);
809 rc = lgss_krb5_set_ccache_name(kcred->kc_ccname);
811 logmsg(LL_ERR, "can't set krb5 ccache name: %s\n",
818 int lgss_krb5_prepare_cred(struct lgss_cred *cred)
820 struct lgss_krb5_cred *kcred;
823 kcred = malloc(sizeof(*kcred));
825 logmsg(LL_ERR, "can't allocate krb5 cred\n");
829 kcred->kc_ccname[0] = '\0';
830 kcred->kc_remove = 0;
831 cred->lc_mech_cred = kcred;
833 if (cred->lc_root_flags != 0) {
834 if (lgss_krb5_get_local_realm())
837 rc = lkrb5_prepare_root_cred(cred);
839 rc = lkrb5_prepare_user_cred(cred);
846 void lgss_krb5_release_cred(struct lgss_cred *cred)
848 struct lgss_krb5_cred *kcred;
850 kcred = (struct lgss_krb5_cred *) cred->lc_mech_cred;
853 cred->lc_mech_cred = NULL;
856 struct lgss_mech_type lgss_mech_krb5 =
859 .lmt_mech_n = LGSS_MECH_KRB5,
860 .lmt_prepare_cred = lgss_krb5_prepare_cred,
861 .lmt_release_cred = lgss_krb5_release_cred,