Whamcloud - gitweb
branch: HEAD
[fs/lustre-release.git] / lustre / utils / gss / lgss_krb5_utils.c
diff --git a/lustre/utils/gss/lgss_krb5_utils.c b/lustre/utils/gss/lgss_krb5_utils.c
new file mode 100644 (file)
index 0000000..0a4d44d
--- /dev/null
@@ -0,0 +1,793 @@
+/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
+ * vim:expandtab:shiftwidth=8:tabstop=8:
+ *
+ * Modifications for Lustre
+ * Copyright 2007, Cluster File Systems, Inc.
+ * All rights reserved
+ * Author: Eric Mei <ericm@clusterfs.com>
+ */
+
+/*
+ *  Adapted in part from MIT Kerberos 5-1.2.1 slave/kprop.c and from
+ *  http://docs.sun.com/?p=/doc/816-1331/6m7oo9sms&a=view
+ *
+ *  Copyright (c) 2002-2004 The Regents of the University of Michigan.
+ *  All rights reserved.
+ *
+ *  Andy Adamson <andros@umich.edu>
+ *  J. Bruce Fields <bfields@umich.edu>
+ *  Marius Aamodt Eriksen <marius@umich.edu>
+ *  Kevin Coffman <kwc@umich.edu>
+ */
+
+/*
+ * slave/kprop.c
+ *
+ * Copyright 1990,1991 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ */
+
+/*
+ * Copyright 1994 by OpenVision Technologies, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without fee,
+ * provided that the above copyright notice appears in all copies and
+ * that both that copyright notice and this permission notice appear in
+ * supporting documentation, and that the name of OpenVision not be used
+ * in advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission. OpenVision makes no
+ * representations about the suitability of this software for any
+ * purpose.  It is provided "as is" without express or implied warranty.
+ *
+ * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
+ * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+/*
+  krb5_util.c
+
+  Copyright (c) 2004 The Regents of the University of Michigan.
+  All rights reserved.
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions
+  are met:
+
+  1. Redistributions of source code must retain the above copyright
+     notice, this list of conditions and the following disclaimer.
+  2. Redistributions in binary form must reproduce the above copyright
+     notice, this list of conditions and the following disclaimer in the
+     documentation and/or other materials provided with the distribution.
+  3. Neither the name of the University nor the names of its
+     contributors may be used to endorse or promote products derived
+     from this software without specific prior written permission.
+
+  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+  DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include "config.h"
+#include <sys/param.h>
+//#include <rpc/rpc.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netdb.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <time.h>
+#include <gssapi/gssapi.h>
+#ifdef USE_PRIVATE_KRB5_FUNCTIONS
+#include <gssapi/gssapi_krb5.h>
+#endif
+#include <krb5.h>
+
+#include "lgss_utils.h"
+#include "lgss_krb5_utils.h"
+
+static void lgss_krb5_mutex_lock(void)
+{
+        if (lgss_mutex_lock(LGSS_MUTEX_KRB5)) {
+                logmsg(LL_ERR, "can't lock process, abort!\n");
+                exit(-1);
+        }
+}
+
+static void lgss_krb5_mutex_unlock(void)
+{
+        if (lgss_mutex_unlock(LGSS_MUTEX_KRB5)) {
+                logmsg(LL_WARN, "can't unlock process, other processes "
+                       "might need to wait long time\n");
+        }
+}
+
+/*
+ * NOTE
+ *  - currently we only support "normal" cache types: "FILE" and "MEMORY".
+ */
+
+#define krb5_err_msg(code)      error_message(code)
+
+const char *krb5_cc_type_mem    = "MEMORY:";
+const char *krb5_cc_type_file   = "FILE:";
+
+char    *krb5_this_realm        = NULL;
+char    *krb5_keytab_file       = "/etc/krb5.keytab";
+char    *krb5_cc_type           = "FILE:";
+char    *krb5_cc_dir            = "/tmp";
+char    *krb5_cred_prefix       = "krb5cc_";
+char    *krb5_cred_root_suffix  = "lustre_root";
+
+struct lgss_krb5_cred {
+        char            kc_ccname[128];
+        int             kc_remove;        /* remove cache upon release */
+};
+
+static
+int lgss_krb5_set_ccache_name(const char *ccname)
+{
+#ifdef USE_GSS_KRB5_CCACHE_NAME
+        unsigned int    maj_stat, min_stat;
+
+        maj_stat = gss_krb5_ccache_name(&min_stat, ccname, NULL);
+        if (maj_stat != GSS_S_COMPLETE) {
+                logmsg(LL_ERR, "failed to set ccache name\n");
+                return -1;
+        }
+#else
+        /*
+         * Set the KRB5CCNAME environment variable to tell the krb5 code
+         * which credentials cache to use.  (Instead of using the private
+         * function above for which there is no generic gssapi equivalent)
+         */
+        if (setenv("KRB5CCNAME", ccname, 1)) {
+                logmsg(LL_ERR, "set env of krb5 ccname: %s\n",
+                       strerror(errno));
+                return -1;
+        }
+#endif
+        logmsg(LL_DEBUG, "set cc: %s\n", ccname);
+        return 0;
+}
+
+static
+int lgss_krb5_get_local_realm(void)
+{
+        krb5_context    context = NULL;
+        krb5_error_code code;
+        int             retval = -1;
+
+        if (krb5_this_realm != NULL)
+                return 0;
+
+        code = krb5_init_context(&context);
+        if (code) {
+                logmsg(LL_ERR, "init ctx: %s\n", krb5_err_msg(code));
+                return -1;
+        }
+
+        code = krb5_get_default_realm(context, &krb5_this_realm);
+        if (code) {
+                logmsg(LL_ERR, "get default realm: %s\n", krb5_err_msg(code));
+                goto out;
+        }
+
+        logmsg(LL_DEBUG, "Local realm: %s\n", krb5_this_realm);
+        retval = 0;
+out:
+        krb5_free_context(context);
+        return retval;
+}
+
+static
+int princ_is_local_realm(krb5_context ctx, krb5_principal princ)
+{
+        return (lgss_krb5_strcasecmp(krb5_princ_realm(ctx, princ),
+                                     krb5_this_realm) == 0);
+}
+
+static
+int svc_princ_is_local_host(krb5_context ctx,
+                            krb5_principal princ,
+                            loglevel_t loglevel)
+{
+        struct utsname utsbuf;
+        struct hostent *host;
+
+        if (krb5_princ_component(ctx, princ, 1) == NULL) {
+                logmsg(loglevel, "service principal has no host part\n");
+                return -1;
+        }
+
+        if (uname(&utsbuf)) {
+                logmsg(loglevel, "get UTS name: %s\n", strerror(errno));
+                return -1;
+        }
+
+        host = gethostbyname(utsbuf.nodename);
+        if (host == NULL) {
+                logmsg(loglevel, "failed to get local hostname\n");
+                return -1;
+        }
+
+        if (lgss_krb5_strcasecmp(krb5_princ_component(ctx, princ, 1),
+                                 host->h_name)) {
+                logmsg(loglevel, "service principal: hostname %.*s "
+                       "doesn't match localhost %s\n",
+                       krb5_princ_component(ctx, princ, 1)->length,
+                       krb5_princ_component(ctx, princ, 1)->data,
+                       host->h_name);
+                return -1;
+        }
+
+        return 0;
+}
+
+static
+int lkrb5_cc_check_tgt_princ(krb5_context ctx,
+                             krb5_ccache ccache,
+                             krb5_principal princ)
+{
+        logmsg(LL_TRACE, "principal: realm %.*s, type %d, size %d, name %.*s\n",
+               krb5_princ_realm(ctx, princ)->length,
+               krb5_princ_realm(ctx, princ)->data,
+               krb5_princ_type(ctx, princ),
+               krb5_princ_size(ctx, princ),
+               krb5_princ_name(ctx, princ)->length,
+               krb5_princ_name(ctx, princ)->data);
+
+        /* check type */
+        if (krb5_princ_type(ctx, princ) != KRB5_NT_PRINCIPAL) {
+                logmsg(LL_WARN, "principal type %d is not I want\n",
+                       krb5_princ_type(ctx, princ));
+                return -1;
+        }
+
+        /* check local realm */
+        if (!princ_is_local_realm(ctx, princ)) {
+                logmsg(LL_WARN, "principal realm %.*s not local: %s\n",
+                       krb5_princ_realm(ctx, princ)->length,
+                       krb5_princ_realm(ctx, princ)->data,
+                       krb5_this_realm);
+                return -1;
+        }
+
+        /* if it's mds service principal, check hostname */
+        if (lgss_krb5_strcmp(krb5_princ_name(ctx, princ),
+                             LGSS_SVC_MDS_STR) == 0) {
+                if (svc_princ_is_local_host(ctx, princ, LL_WARN)) {
+                        logmsg(LL_WARN, "mds service principal not belongs "
+                               "to this node\n");
+                        return -1;
+                }
+        } else if (lgss_krb5_strcmp(krb5_princ_name(ctx, princ),
+                                    LGSS_USR_ROOT_STR)) {
+                /* do nothing */
+        } else {
+                logmsg(LL_WARN, "unexpected krb5 cc principal name %.*s\n",
+                       krb5_princ_name(ctx, princ)->length,
+                       krb5_princ_name(ctx, princ)->data);
+                return -1;
+        }
+
+        logmsg(LL_TRACE, "principal is OK\n");
+        return 0;
+}
+
+/*
+ * find out whether current TGT cache is valid or not
+ */
+static
+int lkrb5_check_root_tgt_cc(krb5_context ctx,
+                            krb5_ccache ccache,
+                            char *ccname)
+{
+        struct stat             statbuf;
+        krb5_ccache             tgt_ccache;
+        krb5_creds              cred;
+        krb5_principal          princ;
+        krb5_cc_cursor          cursor;
+        krb5_error_code         code;
+        char                   *ccfile;
+        time_t                  now;
+        int                     rc = -1, found = 0;
+
+        if (strncmp(ccname, krb5_cc_type, strlen(krb5_cc_type_file))) {
+                logmsg(LL_ERR, "unexpected cc type\n");
+                return -1;
+        }
+
+        ccfile = ccname + strlen(krb5_cc_type_file);
+        logmsg(LL_TRACE, "cc file name: %s\n", ccfile);
+
+        /* firstly make sure the cache file is there */
+        if (stat(ccfile, &statbuf)) {
+                logmsg(LL_DEBUG, "krb5 cc %s: %s\n", ccname, strerror(errno));
+                return -1;
+        }
+
+        /* prepare parsing the cache file */
+        code = krb5_cc_resolve(ctx, ccname, &tgt_ccache);
+        if (code) {
+                logmsg(LL_ERR, "resolve krb5 cc %s: %s\n",
+                       ccname, krb5_err_msg(code));
+                return -1;
+        }
+
+        /* checks the principal */
+        code = krb5_cc_get_principal(ctx, tgt_ccache, &princ);
+        if (code) {
+                logmsg(LL_ERR, "get cc principal: %s\n", krb5_err_msg(code));
+                goto out_cc;
+        }
+
+        if (lkrb5_cc_check_tgt_princ(ctx, tgt_ccache, princ)) {
+                logmsg(LL_WARN, "cc principal is not valid\n");
+                goto out_princ;
+        }
+
+        /*
+         * find a valid entry
+         */
+        code = krb5_cc_start_seq_get(ctx, tgt_ccache, &cursor);
+        if (code) {
+                logmsg(LL_ERR, "start cc iteration: %s\n", krb5_err_msg(code));
+                goto out_princ;
+        }
+
+        now = time(0);
+        do {
+                krb5_timestamp  duration, delta;
+
+                code = krb5_cc_next_cred(ctx, tgt_ccache, &cursor, &cred);
+                if (code != 0)
+                        break;
+
+                logmsg(LL_DEBUG, "cred: server realm %.*s, type %d, name %.*s; "
+                       "time (%d-%d, renew till %d), valid %d\n",
+                       krb5_princ_realm(ctx, cred.server)->length,
+                       krb5_princ_realm(ctx, cred.server)->data,
+                       krb5_princ_type(ctx, cred.server),
+                       krb5_princ_name(ctx, cred.server)->length,
+                       krb5_princ_name(ctx, cred.server)->data,
+                       cred.times.starttime, cred.times.endtime,
+                       cred.times.renew_till, cred.times.endtime - now);
+
+                /* FIXME
+                 * we found the princ type is always 0 (KRB5_NT_UNKNOWN), why???
+                 */
+
+                /* FIXME how about inter-realm TGT??? FIXME */
+                if (lgss_krb5_strcasecmp(krb5_princ_name(ctx, cred.server),
+                                         "krbtgt"))
+                        continue;
+
+                if (lgss_krb5_strcasecmp(krb5_princ_realm(ctx, cred.server),
+                                         krb5_this_realm))
+                        continue;
+
+                /* check validity of time */
+                delta = 60 * 30; /* half an hour */
+                duration = cred.times.endtime - cred.times.starttime;
+                if (duration / 4 < delta)
+                        delta = duration / 4;
+
+                if (cred.times.starttime <= now &&
+                    cred.times.endtime >= now + delta) {
+                        found = 1;
+                        break;
+                }
+        } while (1);
+
+        if (!found) {
+                logmsg(LL_DEBUG, "doesn't find good TGT cache\n");
+                goto out_seq;
+        }
+
+        /* found a good cred, store it into @ccache */
+        logmsg(LL_DEBUG, "found good TGT cache\n");
+
+        code = krb5_cc_initialize(ctx, ccache, princ);
+        if (code) {
+                logmsg(LL_ERR, "init private cc: %s\n", krb5_err_msg(code));
+                goto out_seq;
+        }
+
+        code = krb5_cc_store_cred(ctx, ccache, &cred);
+        if (code) {
+                logmsg(LL_ERR, "store private cred: %s\n", krb5_err_msg(code));
+                goto out_seq;
+        }
+
+        logmsg(LL_DEBUG, "store private ccache OK\n");
+        rc = 0;
+
+out_seq:
+        krb5_cc_end_seq_get(ctx, tgt_ccache, &cursor);
+out_princ:
+        krb5_free_principal(ctx, princ);
+out_cc:
+        krb5_cc_close(ctx, tgt_ccache);
+
+        return rc;
+}
+
+static
+int lkrb5_get_root_tgt_keytab(krb5_context ctx,
+                              krb5_ccache ccache,
+                              krb5_keytab kt,
+                              krb5_principal princ,
+                              const char *ccname)
+{
+        krb5_get_init_creds_opt opts;
+        krb5_creds              cred;
+        krb5_ccache             tgt_ccache;
+        krb5_error_code         code;
+        int                     rc = -1;
+
+        krb5_get_init_creds_opt_init(&opts);
+        krb5_get_init_creds_opt_set_address_list(&opts, NULL);
+        /*
+         * by default krb5 library obtain ticket with lifetime shorter
+         * than the max value. we can change it here if we want. but
+         * seems not necessary now.
+         *
+        krb5_get_init_creds_opt_set_tkt_life(&opts, very-long-time);
+         *
+         */
+
+        /*
+         * obtain TGT and store into global ccache
+         */
+        code = krb5_get_init_creds_keytab(ctx, &cred, princ, kt,
+                                          0, NULL, &opts);
+        if (code) {
+                logmsg(LL_ERR, "failed to get root TGT for "
+                       "principal %.*s: %s\n",
+                       krb5_princ_name(ctx, princ)->length,
+                       krb5_princ_name(ctx, princ)->data,
+                       krb5_err_msg(code));
+                return -1;
+        }
+
+        code = krb5_cc_resolve(ctx, ccname, &tgt_ccache);
+        if (code) {
+                logmsg(LL_ERR, "resolve cc %s: %s\n",
+                       ccname, krb5_err_msg(code));
+                goto out_cred;
+        }
+
+        code = krb5_cc_initialize(ctx, tgt_ccache, princ);
+        if (code) {
+                logmsg(LL_ERR, "initialize cc %s: %s\n",
+                       ccname, krb5_err_msg(code));
+                goto out_cc;
+        }
+
+        code = krb5_cc_store_cred(ctx, tgt_ccache, &cred);
+        if (code) {
+                logmsg(LL_ERR, "store cred to cc %s: %s\n",
+                       ccname, krb5_err_msg(code));
+                goto out_cc;
+        }
+
+        logmsg(LL_INFO, "installed TGT of %.*s in cc %s\n",
+               krb5_princ_name(ctx, princ)->length,
+               krb5_princ_name(ctx, princ)->data,
+               ccname);
+
+        /*
+         * now store the cred into my own cc too
+         */
+        code = krb5_cc_initialize(ctx, ccache, princ);
+        if (code) {
+                logmsg(LL_ERR, "init mem cc: %s\n", krb5_err_msg(code));
+                goto out_cc;
+        }
+
+        code = krb5_cc_store_cred(ctx, ccache, &cred);
+        if (code) {
+                logmsg(LL_ERR, "store mm cred: %s\n", krb5_err_msg(code));
+                goto out_cc;
+        }
+
+        logmsg(LL_DEBUG, "stored TGT into mem cc OK\n");
+        rc = 0;
+out_cc:
+        krb5_cc_close(ctx, tgt_ccache);
+out_cred:
+        krb5_free_cred_contents(ctx, &cred);
+        return rc;
+}
+
+/*
+ * obtain a new root TGT
+ */
+static
+int lkrb5_refresh_root_tgt_cc(krb5_context ctx,
+                              krb5_ccache ccache,
+                              const char *ccname)
+{
+        krb5_keytab             kt;
+        krb5_keytab_entry       kte;
+        krb5_kt_cursor          cursor;
+        krb5_principal          princ = NULL;
+        krb5_error_code         code;
+        int                     rc = -1;
+
+        /* prepare parsing the keytab file */
+        code = krb5_kt_resolve(ctx, krb5_keytab_file, &kt);
+        if (code) {
+                logmsg(LL_ERR, "resolve keytab %s: %s\n",
+                       krb5_keytab_file, krb5_err_msg(code));
+                return -1;
+        }
+
+        code = krb5_kt_start_seq_get(ctx, kt, &cursor);
+        if (code) {
+                logmsg(LL_ERR, "start kt iteration: %s\n", krb5_err_msg(code));
+                goto out_kt;
+        }
+
+        /* iterate keytab to find proper a entry */
+        do {
+                code = krb5_kt_next_entry(ctx, kt, &kte, &cursor);
+                if (code != 0)
+                        break;
+
+                logmsg(LL_TRACE, "kt entry: realm %.*s, type %d, "
+                       "size %d, name %.*s\n",
+                       krb5_princ_realm(ctx, kte.principal)->length,
+                       krb5_princ_realm(ctx, kte.principal)->data,
+                       krb5_princ_type(ctx, kte.principal),
+                       krb5_princ_size(ctx, kte.principal),
+                       krb5_princ_name(ctx, kte.principal)->length,
+                       krb5_princ_name(ctx, kte.principal)->data);
+
+                if (!princ_is_local_realm(ctx, kte.principal))
+                        continue;
+
+                /* lustre_root@realm */
+                if (lgss_krb5_strcmp(krb5_princ_name(ctx, kte.principal),
+                                     LGSS_USR_ROOT_STR) == 0) {
+                        if (princ != NULL) {
+                                logmsg(LL_WARN, "already picked one? "
+                                       "how could it possible???\n");
+                                continue;
+                        }
+
+                        code = krb5_copy_principal(ctx, kte.principal, &princ);
+                        if (code)
+                                logmsg(LL_ERR, "copy lustre_root princ: %s\n",
+                                       krb5_err_msg(code));
+
+                        continue;
+                }
+
+                /* lustre_mds/host@realm */
+                if (lgss_krb5_strcmp(krb5_princ_name(ctx, kte.principal),
+                                     LGSS_SVC_MDS_STR) == 0) {
+                        krb5_principal  princ2;
+
+                        if (svc_princ_is_local_host(ctx, kte.principal,
+                                                    LL_TRACE)) {
+                                logmsg(LL_TRACE, "mds service principal: "
+                                       "not belongs to this node\n");
+                                continue;
+                        }
+
+                        /* select this one */
+                        code = krb5_copy_principal(ctx, kte.principal, &princ2);
+                        if (code) {
+                                logmsg(LL_ERR, "copy lustre_mds princ: %s\n",
+                                       krb5_err_msg(code));
+                                continue;
+                        }
+
+                        if (princ != NULL) {
+                                logmsg(LL_TRACE, "release a lustre_root one\n");
+                                krb5_free_principal(ctx, princ);
+                        }
+                        princ = princ2;
+                        break;
+                }
+        } while (1);
+
+        krb5_kt_end_seq_get(ctx, kt, &cursor);
+
+        if (princ == NULL) {
+                logmsg(LL_ERR, "can't find proper keytab entry\n");
+                goto out_kt;
+        }
+
+        /* obtain root TGT */
+        rc = lkrb5_get_root_tgt_keytab(ctx, ccache, kt, princ, ccname);
+
+        krb5_free_principal(ctx, princ);
+out_kt:
+        krb5_kt_close(ctx, kt);
+        return rc;
+}
+
+static
+int lkrb5_prepare_root_cred(struct lgss_cred *cred)
+{
+        krb5_context            ctx;
+        krb5_ccache             ccache;
+        krb5_error_code         code;
+        struct lgss_krb5_cred  *kcred;
+        char                    tgtcc[1024];
+        int                     rc = -1;
+
+        lassert(krb5_this_realm != NULL);
+
+        kcred = (struct lgss_krb5_cred *) cred->lc_mech_cred;
+
+        /* compose the TGT cc name */
+        snprintf(tgtcc, sizeof(tgtcc), "%s%s/%s%s_%s",
+                 krb5_cc_type, krb5_cc_dir, krb5_cred_prefix,
+                 krb5_cred_root_suffix, krb5_this_realm);
+        logmsg(LL_DEBUG, "root krb5 TGT ccname: %s\n", tgtcc);
+
+        /* compose the memory cc name, since the only user is myself,
+         * the name could be fixed
+         */
+        snprintf(kcred->kc_ccname, sizeof(kcred->kc_ccname),
+                 "%s/self", krb5_cc_type_mem);
+        logmsg(LL_TRACE, "private cc: %s\n", kcred->kc_ccname);
+
+        code = krb5_init_context(&ctx);
+        if (code) {
+                logmsg(LL_ERR, "initialize krb5 context: %s\n",
+                       krb5_err_msg(code));
+                return -1;
+        }
+
+        code = krb5_cc_resolve(ctx, kcred->kc_ccname, &ccache);
+        if (code) {
+                logmsg(LL_ERR, "resolve krb5 cc %s: %s\n",
+                       kcred->kc_ccname, krb5_err_msg(code));
+                goto out_ctx;
+        }
+
+        /*
+         * search and/or obtain root TGT credential.
+         * it touched global (on-disk) tgt cache, do it inside mutex locking
+         */
+        lgss_krb5_mutex_lock();
+
+        rc = lkrb5_check_root_tgt_cc(ctx, ccache, tgtcc);
+        if (rc != 0)
+                rc = lkrb5_refresh_root_tgt_cc(ctx, ccache, tgtcc);
+
+        if (rc == 0)
+                rc = lgss_krb5_set_ccache_name(kcred->kc_ccname);
+
+        lgss_krb5_mutex_unlock();
+
+        krb5_cc_close(ctx, ccache);
+out_ctx:
+        krb5_free_context(ctx);
+
+        logmsg(LL_DEBUG, "prepare root credentail %s\n", rc ? "failed" : "OK");
+        return rc;
+}
+
+static
+int lkrb5_prepare_user_cred(struct lgss_cred *cred)
+{
+        struct lgss_krb5_cred   *kcred;
+        int                      rc;
+
+        lassert(krb5_this_realm == NULL);
+
+        kcred = (struct lgss_krb5_cred *) cred->lc_mech_cred;
+
+        /*
+         * here we just specified a fix ccname, instead of searching
+         * entire cc dir. is this OK??
+         */
+        snprintf(kcred->kc_ccname, sizeof(kcred->kc_ccname),
+                 "%s%s/%s%u",
+                 krb5_cc_type, krb5_cc_dir, krb5_cred_prefix, cred->lc_uid);
+        logmsg(LL_DEBUG, "using krb5 cache name: %s\n", kcred->kc_ccname);
+
+        rc = lgss_krb5_set_ccache_name(kcred->kc_ccname);
+        if (rc)
+                logmsg(LL_ERR, "can't set krb5 ccache name: %s\n",
+                       kcred->kc_ccname);
+
+        return rc;
+}
+
+static
+int lgss_krb5_prepare_cred(struct lgss_cred *cred)
+{
+        struct lgss_krb5_cred  *kcred;
+        int                     rc;
+
+        kcred = malloc(sizeof(*kcred));
+        if (kcred == NULL) {
+                logmsg(LL_ERR, "can't allocate krb5 cred\n");
+                return -1;
+        }
+
+        kcred->kc_ccname[0] = '\0';
+        kcred->kc_remove = 0;
+        cred->lc_mech_cred = kcred;
+
+        if (cred->lc_fl_root || cred->lc_fl_mds) {
+                if (lgss_krb5_get_local_realm())
+                        return -1;
+
+                rc = lkrb5_prepare_root_cred(cred);
+        } else {
+                rc = lkrb5_prepare_user_cred(cred);
+        }
+
+        return rc;
+}
+
+static
+void lgss_krb5_release_cred(struct lgss_cred *cred)
+{
+        struct lgss_krb5_cred   *kcred;
+
+        kcred = (struct lgss_krb5_cred *) cred->lc_mech_cred;
+
+        free(kcred);
+        cred->lc_mech_cred = NULL;
+}
+
+struct lgss_mech_type lgss_mech_krb5 = 
+{
+        .lmt_name               = "krb5",
+        .lmt_mech_n             = LGSS_MECH_KRB5,
+        .lmt_prepare_cred       = lgss_krb5_prepare_cred,
+        .lmt_release_cred       = lgss_krb5_release_cred,
+};