--- /dev/null
+/*
+ * 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 "gssd.h"
+#include "err_util.h"
+#include "gss_util.h"
+#include "gss_oids.h"
+#include "krb5_util.h"
+
+/* Global list of principals/cache file names for machine credentials */
+struct gssd_k5_kt_princ *gssd_k5_kt_princ_list = NULL;
+
+/* Encryption types supported by the kernel rpcsec_gss code */
+int num_krb5_enctypes = 0;
+krb5_enctype *krb5_enctypes = NULL;
+
+/* credential expire time in advance */
+unsigned long machine_cred_expire_advance = 300; /* 5 mins */
+
+/*==========================*/
+/*=== Internal routines ===*/
+/*==========================*/
+
+static int select_krb5_ccache(const struct dirent *d);
+static int gssd_find_existing_krb5_ccache(uid_t uid, struct dirent **d);
+static int gssd_get_single_krb5_cred(krb5_context context,
+ krb5_keytab kt, struct gssd_k5_kt_princ *ple);
+static int gssd_process_krb5_keytab(krb5_context context, krb5_keytab kt,
+ char *kt_name);
+
+/*
+ * convenient macros, these perhaps need further cleanup
+ */
+#ifdef HAVE_KRB5
+
+#define KEYTAB_ENTRY_MATCH(kte, name) \
+ ( \
+ (kte).principal->data[0].length == (sizeof(name)-1) && \
+ strncmp((kte).principal->data[0].data, (name), sizeof(name)-1) == 0 \
+ )
+#define KRB5_FREE_UNPARSED_NAME(ctx, name) \
+ krb5_free_unparsed_name((ctx), (name));
+#define KRB5_STRDUP(str) \
+ strndup((str).data, (str).length)
+#define KRB5_STRCMP(str, name) \
+ ( \
+ (str)->length != strlen(name) || \
+ strncmp((str)->data, (name), (str)->length) != 0 \
+ )
+#define KRB5_STRCASECMP(str, name) \
+ ( \
+ (str)->length != strlen(name) || \
+ strncasecmp((str)->data, (name), (str)->length) != 0 \
+ )
+
+#else /* !HAVE_KRB5 */
+
+#define KEYTAB_ENTRY_MATCH(kte, name) \
+ ( \
+ strlen((kte).principal->name.name_string.val[0]) == \
+ (sizeof(name)-1) && \
+ strncmp(kte.principal->name.name_string.val[0], (name), \
+ sizeof(name)-1) == 0 \
+ )
+#define KRB5_FREE_UNPARSED_NAME(ctx, name) \
+ free(pname);
+#define KRB5_STRDUP(str) \
+ strdup(str)
+#define KRB5_STRCMP(str, name) \
+ strcmp((str), (name))
+#define KRB5_STRCASECMP(str, name) \
+ strcmp((str), (name))
+
+#endif /* HAVE_KRB5 */
+
+/*
+ * Called from the scandir function to weed out potential krb5
+ * credentials cache files
+ *
+ * Returns:
+ * 0 => don't select this one
+ * 1 => select this one
+ */
+static int
+select_krb5_ccache(const struct dirent *d)
+{
+ /*
+ * Note: We used to check d->d_type for DT_REG here,
+ * but apparenlty reiser4 always has DT_UNKNOWN.
+ * Check for IS_REG after stat() call instead.
+ */
+ if (strstr(d->d_name, GSSD_DEFAULT_CRED_PREFIX))
+ return 1;
+ else
+ return 0;
+}
+
+/*
+ * Look in the ccachedir for files that look like they
+ * are Kerberos Credential Cache files for a given UID. Return
+ * non-zero and the dirent pointer for the entry most likely to be
+ * what we want. Otherwise, return zero and no dirent pointer.
+ * The caller is responsible for freeing the dirent if one is returned.
+ *
+ * Returns:
+ * 0 => could not find an existing entry
+ * 1 => found an existing entry
+ */
+static int
+gssd_find_existing_krb5_ccache(uid_t uid, struct dirent **d)
+{
+ struct dirent **namelist;
+ int n;
+ int i;
+ int found = 0;
+ struct dirent *best_match_dir = NULL;
+ struct stat best_match_stat, tmp_stat;
+
+ memset(&best_match_stat, 0, sizeof(best_match_stat));
+ *d = NULL;
+ n = scandir(ccachedir, &namelist, select_krb5_ccache, 0);
+ if (n < 0) {
+ perror("scandir looking for krb5 credentials caches");
+ }
+ else if (n > 0) {
+ char substring[128];
+ char fullstring[128];
+ char statname[1024];
+ snprintf(substring, sizeof(substring), "_%d_", uid);
+ snprintf(fullstring, sizeof(fullstring), "_%d", uid);
+ for (i = 0; i < n; i++) {
+ printerr(3, "CC file '%s' being considered\n",
+ namelist[i]->d_name);
+ if (strstr(namelist[i]->d_name, substring) ||
+ !strcmp(namelist[i]->d_name, fullstring)) {
+ snprintf(statname, sizeof(statname),
+ "%s/%s", ccachedir,
+ namelist[i]->d_name);
+ if (stat(statname, &tmp_stat)) {
+ printerr(0, "Error doing stat "
+ "on file '%s'\n",
+ statname);
+ continue;
+ }
+ if (!S_ISREG(tmp_stat.st_mode)) {
+ printerr(3, "File '%s' is not "
+ "a regular file\n",
+ statname);
+ continue;
+ }
+ printerr(3, "CC file '%s' matches "
+ "name check and has "
+ "mtime of %u\n",
+ namelist[i]->d_name,
+ tmp_stat.st_mtime);
+ /* if more than one match is found,
+ * return the most recent (the one
+ * with the latest mtime),
+ * and don't free the dirent */
+ if (!found) {
+ best_match_dir = namelist[i];
+ best_match_stat = tmp_stat;
+ found++;
+ }
+ else {
+ /*
+ * If the current match has
+ * an mtime later than the
+ * one we are looking at,
+ * then use the current match.
+ * Otherwise, we still have
+ * the best match.
+ */
+ if (tmp_stat.st_mtime >
+ best_match_stat.st_mtime) {
+ free(best_match_dir);
+ best_match_dir = namelist[i];
+ best_match_stat = tmp_stat;
+ }
+ else {
+ free(namelist[i]);
+ }
+ printerr(3, "CC file '%s' is our "
+ "current best match "
+ "with mtime of %u\n",
+ best_match_dir->d_name,
+ best_match_stat.st_mtime);
+ }
+ }
+ else
+ free(namelist[i]);
+ }
+ free(namelist);
+ }
+ if (found)
+ {
+ *d = best_match_dir;
+ }
+ return found;
+}
+
+
+/*
+ * Obtain credentials via a key in the keytab given
+ * a keytab handle and a gssd_k5_kt_princ structure.
+ * Checks to see if current credentials are expired,
+ * if not, uses the keytab to obtain new credentials.
+ *
+ * Returns:
+ * 0 => success (or credentials have not expired)
+ * nonzero => error
+ */
+static int
+gssd_get_single_krb5_cred(krb5_context context,
+ krb5_keytab kt,
+ struct gssd_k5_kt_princ *ple)
+{
+ krb5_get_init_creds_opt options;
+ krb5_creds my_creds;
+ krb5_ccache ccache = NULL;
+ char kt_name[BUFSIZ];
+ char cc_name[BUFSIZ];
+ int code;
+ time_t now = time(0);
+ char *cache_type;
+
+ memset(&my_creds, 0, sizeof(my_creds));
+
+ if (ple->ccname && ple->endtime > now + machine_cred_expire_advance) {
+ printerr(2, "INFO: Credentials in CC '%s' are good until %d\n",
+ ple->ccname, ple->endtime);
+ code = 0;
+ goto out;
+ }
+
+ if ((code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ))) {
+ printerr(0, "ERROR: Unable to get keytab name in "
+ "gssd_get_single_krb5_cred\n");
+ goto out;
+ }
+
+ krb5_get_init_creds_opt_init(&options);
+ krb5_get_init_creds_opt_set_address_list(&options, NULL);
+
+#ifdef TEST_SHORT_LIFETIME
+ /* set a short lifetime (for debugging only!) */
+ printerr(0, "WARNING: Using (debug) short machine cred lifetime!\n");
+ krb5_get_init_creds_opt_set_tkt_life(&options, 5*60);
+#else
+ /* FIXME try to get the ticket with lifetime as long as possible,
+ * to work around ticket-expiry + recovery problem in cmd3-11
+ * remove this!!!
+ */
+ krb5_get_init_creds_opt_set_tkt_life(&options, 30*24*60*60);
+#endif
+ if ((code = krb5_get_init_creds_keytab(context, &my_creds, ple->princ,
+ kt, 0, NULL, &options))) {
+ char *pname;
+ if ((krb5_unparse_name(context, ple->princ, &pname))) {
+ pname = NULL;
+ }
+ printerr(0, "WARNING: %s while getting initial ticket for "
+ "principal '%s' from keytab '%s'\n",
+ error_message(code),
+ pname ? pname : "<unparsable>", kt_name);
+ if (pname) KRB5_FREE_UNPARSED_NAME(context, pname);
+ goto out;
+ }
+
+ /*
+ * Initialize cache file which we're going to be using
+ */
+
+ if (use_memcache)
+ cache_type = "MEMORY";
+ else
+ cache_type = "FILE";
+ snprintf(cc_name, sizeof(cc_name), "%s:%s/%s%s_%s",
+ cache_type,
+ GSSD_DEFAULT_CRED_DIR, GSSD_DEFAULT_CRED_PREFIX,
+ GSSD_DEFAULT_MACHINE_CRED_SUFFIX, ple->realm);
+ ple->endtime = my_creds.times.endtime;
+ ple->ccname = strdup(cc_name);
+ if (ple->ccname == NULL) {
+ printerr(0, "ERROR: no storage to duplicate credentials "
+ "cache name\n");
+ code = ENOMEM;
+ goto out;
+ }
+ if ((code = krb5_cc_resolve(context, cc_name, &ccache))) {
+ printerr(0, "ERROR: %s while opening credential cache '%s'\n",
+ error_message(code), cc_name);
+ goto out;
+ }
+ if ((code = krb5_cc_initialize(context, ccache, ple->princ))) {
+ printerr(0, "ERROR: %s while initializing credential "
+ "cache '%s'\n", error_message(code), cc_name);
+ goto out;
+ }
+ if ((code = krb5_cc_store_cred(context, ccache, &my_creds))) {
+ printerr(0, "ERROR: %s while storing credentials in '%s'\n",
+ error_message(code), cc_name);
+ goto out;
+ }
+
+ code = 0;
+ printerr(1, "Using (machine) credentials cache: '%s'\n", cc_name);
+ out:
+ if (ccache)
+ krb5_cc_close(context, ccache);
+ krb5_free_cred_contents(context, &my_creds);
+ return (code);
+}
+
+static struct gssd_k5_kt_princ * gssd_get_realm_ple(void *r)
+{
+ struct gssd_k5_kt_princ *ple;
+#ifdef HAVE_KRB5
+ krb5_data *realm = (krb5_data *)r;
+#else
+ char *realm = (char *)r;
+#endif
+
+ for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
+ if (KRB5_STRCMP(realm, ple->realm) == 0)
+ return ple;
+ }
+ return NULL;
+}
+
+static void gssd_free_ple(krb5_context kctx, struct gssd_k5_kt_princ *ple)
+{
+ if (ple->princ)
+ krb5_free_principal(kctx, ple->princ);
+ if (ple->realm)
+ free(ple->realm);
+ if (ple->ccname)
+ free(ple->ccname);
+ free(ple);
+}
+
+static int gssd_remove_ple(krb5_context kctx, struct gssd_k5_kt_princ *ple)
+{
+ struct gssd_k5_kt_princ **prev = &gssd_k5_kt_princ_list;
+ struct gssd_k5_kt_princ *ent = gssd_k5_kt_princ_list;
+
+ for (; ent; prev = &ent->next, ent = ent->next) {
+ if (ent != ple)
+ continue;
+
+ *prev = ent->next;
+ gssd_free_ple(kctx, ent);
+ return 1;
+ }
+ return 0;
+}
+
+static
+struct gssd_k5_kt_princ *gssd_create_ple(krb5_context kctx,
+ krb5_principal principal)
+{
+ struct gssd_k5_kt_princ *ple;
+ krb5_error_code code;
+
+ ple = malloc(sizeof(*ple));
+ if (ple == NULL) {
+ printerr(0, "ERROR: could not allocate storage "
+ "for principal list entry\n");
+ return NULL;
+ }
+
+ memset(ple, 0, sizeof(*ple));
+
+ ple->realm = KRB5_STRDUP(principal->realm);
+ if (ple->realm == NULL) {
+ printerr(0, "ERROR: not enough memory while copying realm to "
+ "principal list entry\n");
+ goto err_free;
+ }
+
+ code = krb5_copy_principal(kctx, principal, &ple->princ);
+ if (code) {
+ printerr(0, "ERROR: %s while copying principal "
+ "to principal list entry\n",
+ error_message(code));
+ goto err_free;
+ }
+
+ return ple;
+err_free:
+ gssd_free_ple(kctx, ple);
+ return NULL;
+}
+
+/*
+ * Process the given keytab file and create a list of principals we
+ * might use to perform mount operations.
+ *
+ * Returns:
+ * 0 => Sucess
+ * nonzero => Error
+ */
+static int
+gssd_process_krb5_keytab(krb5_context context, krb5_keytab kt, char *kt_name)
+{
+ krb5_kt_cursor cursor;
+ krb5_keytab_entry kte;
+ krb5_error_code code;
+ struct gssd_k5_kt_princ *ple;
+ int retval = -1;
+
+ /*
+ * Look through each entry in the keytab file and determine
+ * if we might want to use it later to do a mount. If so,
+ * save info in the global principal list
+ * (gssd_k5_kt_princ_list).
+ * Note: (ple == principal list entry)
+ */
+ if ((code = krb5_kt_start_seq_get(context, kt, &cursor))) {
+ printerr(0, "ERROR: %s while beginning keytab scan "
+ "for keytab '%s'\n",
+ error_message(code), kt_name);
+ retval = code;
+ goto out;
+ }
+
+ while ((code = krb5_kt_next_entry(context, kt, &kte, &cursor)) == 0) {
+ char *pname;
+ if ((code = krb5_unparse_name(context, kte.principal,
+ &pname))) {
+ printerr(0, "WARNING: Skipping keytab entry because "
+ "we failed to unparse principal name: %s\n",
+ error_message(code));
+ continue;
+ }
+ printerr(2, "Processing keytab entry for principal '%s'\n",
+ pname);
+
+ /* mds service entry:
+ * - hostname and realm should match this node
+ * - replace existing non-mds entry of this realm
+ */
+ if (KEYTAB_ENTRY_MATCH(kte, GSSD_SERVICE_MDS)) {
+ krb5_principal princ = kte.principal;
+ krb5_data *princ_host;
+ struct utsname utsbuf;
+ struct hostent *host;
+
+ if (KRB5_STRCASECMP(krb5_princ_realm(context, princ),
+ this_realm) != 0) {
+ printerr(2, "alien mds service entry, skip\n");
+ goto next;
+ }
+
+ princ_host = krb5_princ_component(context, princ, 1);
+ if (princ_host == NULL) {
+ printerr(2, "mds service entry: no hostname in "
+ "principal, skip\n");
+ goto next;
+ }
+
+ if (uname(&utsbuf)) {
+ printerr(2, "mds service entry: unable to get "
+ "UTS name, skip\n");
+ goto next;
+ }
+ host = gethostbyname(utsbuf.nodename);
+ if (host == NULL) {
+ printerr(2, "mds service entry: unable to get "
+ "local hostname, skip\n");
+ goto next;
+ }
+
+ if (KRB5_STRCASECMP(princ_host, host->h_name) != 0) {
+ printerr(2, "mds service entry: hostname "
+ "doesn't match: %s - %.*s, skip\n",
+ host->h_name,
+ princ_host->length, princ_host->data);
+ goto next;
+ }
+
+ ple = gssd_get_realm_ple((void *)&kte.principal->realm);
+ if (ple) {
+ if (ple->fl_mds) {
+ printerr(2,"mds service entry: found a"
+ "duplicated one, it's like a "
+ "mis-configuration, skip\n");
+ goto next;
+ }
+
+ gssd_remove_ple(context, ple);
+ printerr(2, "mds service entry: replace an "
+ "existed non-mds one\n");
+ }
+ } else if (KEYTAB_ENTRY_MATCH(kte, LUSTRE_ROOT_NAME)) {
+ ple = gssd_get_realm_ple((void *)&kte.principal->realm);
+ if (ple) {
+ if (ple->fl_mds || ple->fl_root) {
+ printerr(2, "root entry: found a "
+ "existed %s entry, skip\n",
+ ple->fl_mds ? "mds" : "root");
+ goto next;
+ }
+
+ gssd_remove_ple(context, ple);
+ printerr(2, "root entry: replace an existed "
+ "non-mds non-root one\n");
+ }
+ } else {
+ printerr(2, "We will NOT use this entry (%s)\n",
+ pname);
+ goto next;
+ }
+
+ /* construct ple */
+ printerr(2, "We will use this entry (%s)\n", pname);
+ ple = gssd_create_ple(context, kte.principal);
+ if (ple == NULL) {
+ KRB5_FREE_UNPARSED_NAME(context, pname);
+ goto out;
+ }
+
+ /* add proper flags */
+ if (KEYTAB_ENTRY_MATCH(kte, GSSD_SERVICE_MDS))
+ ple->fl_mds = 1;
+ else if (KEYTAB_ENTRY_MATCH(kte, LUSTRE_ROOT_NAME))
+ ple->fl_root = 1;
+
+ /* enqueue */
+ if (gssd_k5_kt_princ_list == NULL)
+ gssd_k5_kt_princ_list = ple;
+ else {
+ ple->next = gssd_k5_kt_princ_list;
+ gssd_k5_kt_princ_list = ple;
+ }
+ next:
+ KRB5_FREE_UNPARSED_NAME(context, pname);
+ }
+
+ if ((code = krb5_kt_end_seq_get(context, kt, &cursor))) {
+ printerr(0, "WARNING: %s while ending keytab scan for "
+ "keytab '%s'\n",
+ error_message(code), kt_name);
+ }
+
+ retval = 0;
+ out:
+ return retval;
+}
+
+/*
+ * Depending on the version of Kerberos, we either need to use
+ * a private function, or simply set the environment variable.
+ */
+static void
+gssd_set_krb5_ccache_name(char *ccname)
+{
+#ifdef USE_GSS_KRB5_CCACHE_NAME
+ u_int maj_stat, min_stat;
+
+ printerr(2, "using gss_krb5_ccache_name to select krb5 ccache %s\n",
+ ccname);
+ maj_stat = gss_krb5_ccache_name(&min_stat, ccname, NULL);
+ if (maj_stat != GSS_S_COMPLETE) {
+ printerr(0, "WARNING: gss_krb5_ccache_name with "
+ "name '%s' failed (%s)\n",
+ ccname, error_message(min_stat));
+ }
+#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.)
+ */
+ printerr(2, "using environment variable to select krb5 ccache %s\n",
+ ccname);
+ setenv("KRB5CCNAME", ccname, 1);
+#endif
+}
+
+/*
+ * Parse the supported encryption type information
+ */
+static int
+parse_enctypes(char *enctypes)
+{
+ int n = 0;
+ char *curr, *comma;
+ int i;
+
+ /* Just in case this ever gets called more than once */
+ if (krb5_enctypes != NULL) {
+ free(krb5_enctypes);
+ krb5_enctypes = NULL;
+ num_krb5_enctypes = 0;
+ }
+
+ /* count the number of commas */
+ for (curr = enctypes; curr && *curr != '\0'; curr = ++comma) {
+ comma = strchr(curr, ',');
+ if (comma != NULL)
+ n++;
+ else
+ break;
+ }
+ /* If no more commas and we're not at the end, there's one more value */
+ if (*curr != '\0')
+ n++;
+
+ /* Empty string, return an error */
+ if (n == 0)
+ return ENOENT;
+
+ /* Allocate space for enctypes array */
+ if ((krb5_enctypes = (int *) calloc(n, sizeof(int))) == NULL) {
+ return ENOMEM;
+ }
+
+ /* Now parse each value into the array */
+ for (curr = enctypes, i = 0; curr && *curr != '\0'; curr = ++comma) {
+ krb5_enctypes[i++] = atoi(curr);
+ comma = strchr(curr, ',');
+ if (comma == NULL)
+ break;
+ }
+
+ num_krb5_enctypes = n;
+ return 0;
+}
+
+/*==========================*/
+/*=== External routines ===*/
+/*==========================*/
+
+/*
+ * Attempt to find the best match for a credentials cache file
+ * given only a UID. We really need more information, but we
+ * do the best we can.
+ *
+ * Returns:
+ * void
+ */
+void
+gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername)
+{
+ char buf[MAX_NETOBJ_SZ];
+ struct dirent *d;
+
+ printerr(2, "getting credentials for client with uid %u for "
+ "server %s\n", uid, servername);
+ memset(buf, 0, sizeof(buf));
+
+ if (gssd_find_existing_krb5_ccache(uid, &d)) {
+ snprintf(buf, sizeof(buf), "FILE:%s/%s",
+ ccachedir, d->d_name);
+ free(d);
+ }
+ else
+ snprintf(buf, sizeof(buf), "FILE:%s/%s%u",
+ ccachedir, GSSD_DEFAULT_CRED_PREFIX, uid);
+ printerr(2, "using %s as credentials cache for client with "
+ "uid %u for server %s\n", buf, uid, servername);
+ gssd_set_krb5_ccache_name(buf);
+}
+
+/*
+ * Let the gss code know where to find the machine credentials ccache.
+ *
+ * Returns:
+ * void
+ */
+void
+gssd_setup_krb5_machine_gss_ccache(char *ccname)
+{
+ printerr(2, "using %s as credentials cache for machine creds\n",
+ ccname);
+ gssd_set_krb5_ccache_name(ccname);
+}
+
+/*
+ * The first time through this routine, go through the keytab and
+ * determine which keys we will try to use as machine credentials.
+ * Every time through this routine, try to obtain credentials using
+ * the keytab entries selected the first time through.
+ *
+ * Returns:
+ * 0 => obtained one or more credentials
+ * nonzero => error
+ *
+ */
+
+int
+gssd_refresh_krb5_machine_creds(void)
+{
+ krb5_context context = NULL;
+ krb5_keytab kt = NULL;;
+ krb5_error_code code;
+ int retval = -1;
+ struct gssd_k5_kt_princ *ple;
+ int gotone = 0;
+ static int processed_keytab = 0;
+
+
+ code = krb5_init_context(&context);
+ if (code) {
+ printerr(0, "ERROR: %s while initializing krb5 in "
+ "gssd_refresh_krb5_machine_creds\n",
+ error_message(code));
+ retval = code;
+ goto out;
+ }
+
+ printerr(2, "Using keytab file '%s'\n", keytabfile);
+
+ if ((code = krb5_kt_resolve(context, keytabfile, &kt))) {
+ printerr(0, "ERROR: %s while resolving keytab '%s'\n",
+ error_message(code), keytabfile);
+ goto out;
+ }
+
+ /* Only go through the keytab file once. Only print messages once. */
+ if (gssd_k5_kt_princ_list == NULL && !processed_keytab) {
+ processed_keytab = 1;
+ gssd_process_krb5_keytab(context, kt, keytabfile);
+ if (gssd_k5_kt_princ_list == NULL) {
+ printerr(0, "ERROR: No usable keytab entries found in "
+ "keytab '%s'\n", keytabfile);
+ printerr(0, "You must have a valid keytab entry for "
+ "%s/<your.host>@<YOUR.REALM> on MDT nodes, "
+ "and %s@<YOUR.REALM> on client nodes, in "
+ "keytab file %s ?\n",
+ GSSD_SERVICE_MDS, LUSTRE_ROOT_NAME,
+ keytabfile);
+ }
+ }
+
+ /*
+ * If we don't have any keytab entries we liked, then we have a problem
+ */
+ if (gssd_k5_kt_princ_list == NULL) {
+ retval = ENOENT;
+ goto out;
+ }
+
+ /*
+ * Now go through the list of saved entries and get initial
+ * credentials for them (We can't do this while making the
+ * list because it messes up the keytab iteration cursor
+ * when we use the keytab to get credentials.)
+ */
+ for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
+ if ((gssd_get_single_krb5_cred(context, kt, ple)) == 0) {
+ gotone++;
+ }
+ }
+ if (!gotone) {
+ printerr(0, "ERROR: No usable machine credentials obtained\n");
+ goto out;
+ }
+
+ retval = 0;
+ out:
+ if (kt) krb5_kt_close(context, kt);
+ krb5_free_context(context);
+
+ return retval;
+}
+
+
+/*
+ * Return an array of pointers to names of credential cache files
+ * which can be used to try to create gss contexts with a server.
+ *
+ * Returns:
+ * 0 => list is attached
+ * nonzero => error
+ */
+int
+gssd_get_krb5_machine_cred_list(char ***list)
+{
+ char **l;
+ int listinc = 10;
+ int listsize = listinc;
+ int i = 0;
+ int retval;
+ struct gssd_k5_kt_princ *ple;
+
+ /* Assume failure */
+ retval = -1;
+ *list = (char **) NULL;
+
+ /* Refresh machine credentials */
+ if ((retval = gssd_refresh_krb5_machine_creds())) {
+ goto out;
+ }
+
+ if ((l = (char **) malloc(listsize * sizeof(char *))) == NULL) {
+ retval = ENOMEM;
+ goto out;
+ }
+
+ for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
+ if (ple->ccname) {
+ if (i + 1 > listsize) {
+ listsize += listinc;
+ l = (char **)
+ realloc(l, listsize * sizeof(char *));
+ if (l == NULL) {
+ retval = ENOMEM;
+ goto out;
+ }
+ }
+ if ((l[i++] = strdup(ple->ccname)) == NULL) {
+ retval = ENOMEM;
+ goto out;
+ }
+ }
+ }
+ if (i > 0) {
+ l[i] = NULL;
+ *list = l;
+ retval = 0;
+ goto out;
+ }
+ out:
+ return retval;
+}
+
+/*
+ * Frees the list of names returned in get_krb5_machine_cred_list()
+ */
+void
+gssd_free_krb5_machine_cred_list(char **list)
+{
+ char **n;
+
+ if (list == NULL)
+ return;
+ for (n = list; n && *n; n++) {
+ free(*n);
+ }
+ free(list);
+}
+
+/*
+ * Called upon exit. Destroys machine credentials.
+ */
+void
+gssd_destroy_krb5_machine_creds(void)
+{
+ krb5_context context;
+ krb5_error_code code = 0;
+ krb5_ccache ccache;
+ struct gssd_k5_kt_princ *ple;
+
+ code = krb5_init_context(&context);
+ if (code) {
+ printerr(0, "ERROR: %s while initializing krb5\n",
+ error_message(code));
+ goto out;
+ }
+
+ for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
+ if (!ple->ccname)
+ continue;
+ if ((code = krb5_cc_resolve(context, ple->ccname, &ccache))) {
+ printerr(0, "WARNING: %s while resolving credential "
+ "cache '%s' for destruction\n",
+ error_message(code), ple->ccname);
+ continue;
+ }
+
+ if ((code = krb5_cc_destroy(context, ccache))) {
+ printerr(0, "WARNING: %s while destroying credential "
+ "cache '%s'\n",
+ error_message(code), ple->ccname);
+ }
+ }
+ out:
+ krb5_free_context(context);
+}
+
+#if 0
+#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
+/*
+ * this routine obtains a credentials handle via gss_acquire_cred()
+ * then calls gss_krb5_set_allowable_enctypes() to limit the encryption
+ * types negotiated.
+ *
+ * Returns:
+ * 0 => all went well
+ * -1 => there was an error
+ */
+
+int
+limit_krb5_enctypes(struct rpc_gss_sec *sec, uid_t uid)
+{
+ u_int maj_stat, min_stat;
+ gss_cred_id_t credh;
+ gss_OID_set_desc desired_mechs;
+ krb5_enctype enctypes[] = {ENCTYPE_DES_CBC_CRC};
+ int num_enctypes = sizeof(enctypes) / sizeof(enctypes[0]);
+
+ /* We only care about getting a krb5 cred */
+ desired_mechs.count = 1;
+ desired_mechs.elements = &krb5oid;
+
+ maj_stat = gss_acquire_cred(&min_stat, NULL, 0,
+ &desired_mechs, GSS_C_INITIATE,
+ &credh, NULL, NULL);
+
+ if (maj_stat != GSS_S_COMPLETE) {
+ pgsserr("gss_acquire_cred",
+ maj_stat, min_stat, &krb5oid);
+ return -1;
+ }
+
+ /*
+ * If we failed for any reason to produce global
+ * list of supported enctypes, use local default here.
+ */
+ if (krb5_enctypes == NULL)
+ maj_stat = gss_set_allowable_enctypes(&min_stat, credh,
+ &krb5oid, num_enctypes, &enctypes);
+ else
+ maj_stat = gss_set_allowable_enctypes(&min_stat, credh,
+ &krb5oid, num_krb5_enctypes,
+ krb5_enctypes);
+ if (maj_stat != GSS_S_COMPLETE) {
+ pgsserr("gss_set_allowable_enctypes",
+ maj_stat, min_stat, &krb5oid);
+ return -1;
+ }
+ sec->cred = credh;
+
+ return 0;
+}
+#endif /* HAVE_SET_ALLOWABLE_ENCTYPES */
+#endif
+
+/*
+ * Obtain supported enctypes from kernel.
+ * Set defaults if info is not available.
+ */
+void
+gssd_obtain_kernel_krb5_info(void)
+{
+ char enctype_file_name[128];
+ char buf[1024];
+ char enctypes[128];
+ int nscanned;
+ int fd;
+ int use_default_enctypes = 0;
+ int nbytes, numfields;
+ char default_enctypes[] = "1,3,2";
+ int code;
+
+ snprintf(enctype_file_name, sizeof(enctype_file_name),
+ "%s/%s", pipefs_dir, "krb5_info");
+
+ if ((fd = open(enctype_file_name, O_RDONLY)) == -1) {
+ printerr(1, "WARNING: gssd_obtain_kernel_krb5_info: "
+ "Unable to open '%s'. Unable to determine "
+ "Kerberos encryption types supported by the "
+ "kernel; using defaults (%s).\n",
+ enctype_file_name, default_enctypes);
+ use_default_enctypes = 1;
+ goto do_the_parse;
+ }
+ memset(buf, 0, sizeof(buf));
+ if ((nbytes = read(fd, buf, sizeof(buf)-1)) == -1) {
+ printerr(0, "WARNING: gssd_obtain_kernel_krb5_info: "
+ "Error reading Kerberos encryption type "
+ "information file '%s'; using defaults (%s).\n",
+ enctype_file_name, default_enctypes);
+ use_default_enctypes = 1;
+ close(fd);
+ goto do_the_parse;
+ }
+ close(fd);
+ numfields = sscanf(buf, "enctypes: %s\n%n", enctypes, &nscanned);
+ if (numfields < 1) {
+ printerr(0, "WARNING: gssd_obtain_kernel_krb5_info: "
+ "error parsing Kerberos encryption type "
+ "information from file '%s'; using defaults (%s).\n",
+ enctype_file_name, default_enctypes);
+ use_default_enctypes = 1;
+ goto do_the_parse;
+ }
+ if (nbytes > nscanned) {
+ printerr(2, "gssd_obtain_kernel_krb5_info: "
+ "Ignoring extra information, '%s', from '%s'\n",
+ buf+nscanned, enctype_file_name);
+ goto do_the_parse;
+ }
+ do_the_parse:
+ if (use_default_enctypes)
+ strcpy(enctypes, default_enctypes);
+
+ if ((code = parse_enctypes(enctypes)) != 0) {
+ printerr(0, "ERROR: gssd_obtain_kernel_krb5_info: "
+ "parse_enctypes%s failed with code %d\n",
+ use_default_enctypes ? " (with default enctypes)" : "",
+ code);
+ }
+}