From: ericm Date: Mon, 11 Sep 2006 22:05:02 +0000 (+0000) Subject: branch: b_new_cmd X-Git-Tag: v1_8_0_110~486^2~957 X-Git-Url: https://git.whamcloud.com/?a=commitdiff_plain;h=a2cb14ae8ba9afa2e4623bf94ea43db704aa0eb5;p=fs%2Flustre-release.git branch: b_new_cmd land gss/krb5. but for now gss is disabled by default. --- diff --git a/lustre/autoconf/Makefile.am b/lustre/autoconf/Makefile.am index 7a747da..78a6511 100644 --- a/lustre/autoconf/Makefile.am +++ b/lustre/autoconf/Makefile.am @@ -1 +1 @@ -EXTRA_DIST := lustre-core.m4 lustre-version.ac +EXTRA_DIST := lustre-core.m4 lustre-version.ac kerberos5.m4 diff --git a/lustre/autoconf/kerberos5.m4 b/lustre/autoconf/kerberos5.m4 new file mode 100644 index 0000000..1dac9f0 --- /dev/null +++ b/lustre/autoconf/kerberos5.m4 @@ -0,0 +1,105 @@ +dnl Checks for Kerberos +dnl NOTE: while we intend to do generic gss-api, currently we +dnl have a requirement to get an initial Kerberos machine +dnl credential. Thus, the requirement for Kerberos. +dnl The Kerberos gssapi library will be dynamically loaded? +AC_DEFUN([AC_KERBEROS_V5],[ + AC_MSG_CHECKING(for Kerberos v5) + AC_ARG_WITH(krb5, + [AC_HELP_STRING([--with-krb5=DIR], [use Kerberos v5 installation in DIR])], + [ case "$withval" in + yes|no) + krb5_with="" + ;; + *) + krb5_with="$withval" + ;; + esac ] + ) + + for dir in $krb5_with /usr /usr/kerberos /usr/local /usr/local/krb5 \ + /usr/krb5 /usr/heimdal /usr/local/heimdal /usr/athena ; do + dnl This ugly hack brought on by the split installation of + dnl MIT Kerberos on Fedora Core 1 + K5CONFIG="" + if test -f $dir/bin/krb5-config; then + K5CONFIG=$dir/bin/krb5-config + elif test -f "/usr/kerberos/bin/krb5-config"; then + K5CONFIG="/usr/kerberos/bin/krb5-config" + elif test -f "/usr/lib/mit/bin/krb5-config"; then + K5CONFIG="/usr/lib/mit/bin/krb5-config" + fi + if test "$K5CONFIG" != ""; then + KRBCFLAGS=`$K5CONFIG --cflags` + KRBLIBS=`$K5CONFIG --libs gssapi` + K5VERS=`$K5CONFIG --version | head -n 1 | awk '{split($(4),v,"."); if (v@<:@"3"@:>@ == "") v@<:@"3"@:>@ = "0"; print v@<:@"1"@:>@v@<:@"2"@:>@v@<:@"3"@:>@ }'` + AC_DEFINE_UNQUOTED(KRB5_VERSION, $K5VERS, [Define this as the Kerberos version number]) + if test -f $dir/include/gssapi/gssapi_krb5.h -a \ + \( -f $dir/lib/libgssapi_krb5.a -o \ + -f $dir/lib/libgssapi_krb5.so \) ; then + AC_DEFINE(HAVE_KRB5, 1, [Define this if you have MIT Kerberos libraries]) + KRBDIR="$dir" + dnl If we are using MIT K5 1.3.1 and before, we *MUST* use the + dnl private function (gss_krb5_ccache_name) to get correct + dnl behavior of changing the ccache used by gssapi. + dnl Starting in 1.3.2, we *DO NOT* want to use + dnl gss_krb5_ccache_name, instead we want to set KRB5CCNAME + dnl to get gssapi to use a different ccache + if test $K5VERS -le 131; then + AC_DEFINE(USE_GSS_KRB5_CCACHE_NAME, 1, [Define this if the private function, gss_krb5_cache_name, must be used to tell the Kerberos library which credentials cache to use. Otherwise, this is done by setting the KRB5CCNAME environment variable]) + fi + gssapi_lib=gssapi_krb5 + break + dnl The following ugly hack brought on by the split installation + dnl of Heimdal Kerberos on SuSe + elif test \( -f $dir/include/heim_err.h -o\ + -f $dir/include/heimdal/heim_err.h \) -a \ + -f $dir/lib/libroken.a; then + AC_DEFINE(HAVE_HEIMDAL, 1, [Define this if you have Heimdal Kerberos libraries]) + KRBDIR="$dir" + gssapi_lib=gssapi + break + fi + fi + done + dnl We didn't find a usable Kerberos environment + if test "x$KRBDIR" = "x"; then + if test "x$krb5_with" = "x"; then + AC_MSG_ERROR(Kerberos v5 with GSS support not found: consider --disable-gss or --with-krb5=) + else + AC_MSG_ERROR(Kerberos v5 with GSS support not found at $krb5_with) + fi + fi + AC_MSG_RESULT($KRBDIR) + + dnl Check if -rpath=$(KRBDIR)/lib is needed + echo "The current KRBDIR is $KRBDIR" + if test "$KRBDIR/lib" = "/lib" -o "$KRBDIR/lib" = "/usr/lib" \ + -o "$KRBDIR/lib" = "//lib" -o "$KRBDIR/lib" = "/usr//lib" ; then + KRBLDFLAGS=""; + elif /sbin/ldconfig -p | grep > /dev/null "=> $KRBDIR/lib/"; then + KRBLDFLAGS=""; + else + KRBLDFLAGS="-Wl,-rpath=$KRBDIR/lib" + fi + + dnl Now check for functions within gssapi library + AC_CHECK_LIB($gssapi_lib, gss_krb5_export_lucid_sec_context, + AC_DEFINE(HAVE_LUCID_CONTEXT_SUPPORT, 1, [Define this if the Kerberos GSS library supports gss_krb5_export_lucid_sec_context]), ,$KRBLIBS) + AC_CHECK_LIB($gssapi_lib, gss_krb5_set_allowable_enctypes, + AC_DEFINE(HAVE_SET_ALLOWABLE_ENCTYPES, 1, [Define this if the Kerberos GSS library supports gss_krb5_set_allowable_enctypes]), ,$KRBLIBS) + AC_CHECK_LIB($gssapi_lib, gss_krb5_ccache_name, + AC_DEFINE(HAVE_GSS_KRB5_CCACHE_NAME, 1, [Define this if the Kerberos GSS library supports gss_krb5_ccache_name]), ,$KRBLIBS) + + dnl If they specified a directory and it didn't work, give them a warning + if test "x$krb5_with" != "x" -a "$krb5_with" != "$KRBDIR"; then + AC_MSG_WARN(Using $KRBDIR instead of requested value of $krb5_with for Kerberos!) + fi + + AC_SUBST([KRBDIR]) + AC_SUBST([KRBLIBS]) + AC_SUBST([KRBCFLAGS]) + AC_SUBST([KRBLDFLAGS]) + AC_SUBST([K5VERS]) + +]) diff --git a/lustre/autoconf/lustre-core.m4 b/lustre/autoconf/lustre-core.m4 index 95f6796..dacffa6 100644 --- a/lustre/autoconf/lustre-core.m4 +++ b/lustre/autoconf/lustre-core.m4 @@ -643,6 +643,24 @@ AC_ARG_ENABLE([client], AC_MSG_RESULT([$enable_client])]) # +# LC_CONFIG_GSS +# +# Build gss and related tools of Lustre +# +AC_DEFUN([LC_CONFIG_GSS], +[AC_MSG_CHECKING([whether to enable gss/krb5 support]) +AC_ARG_ENABLE([gss], + AC_HELP_STRING([--enable-gss], + [enable gss/krb5 support]), + [enable_gss='yes'],[enable_gss='no']) +AC_MSG_RESULT([$enable_gss]) +if test x$enable_gss != xno; then + PKG_CHECK_MODULES([GSSAPI], [libgssapi >= 0.10]) + AC_KERBEROS_V5 +fi +]) + +# # LC_CONFIG_LIBLUSTRE # # whether to build liblustre @@ -770,6 +788,7 @@ AM_CONDITIONAL(LIBLUSTRE_TESTS, test x$enable_liblustre_tests = xyes) AM_CONDITIONAL(MPITESTS, test x$enable_mpitests = xyes, Build MPI Tests) AM_CONDITIONAL(CLIENT, test x$enable_client = xyes) AM_CONDITIONAL(SERVER, test x$enable_server = xyes) +AM_CONDITIONAL(GSS, test x$enable_gss = xyes) AM_CONDITIONAL(QUOTA, test x$enable_quota = xyes) AM_CONDITIONAL(SPLIT, test x$enable_split = xyes) AM_CONDITIONAL(BLKID, test x$ac_cv_header_blkid_blkid_h = xyes) @@ -848,12 +867,15 @@ lustre/mgs/Makefile lustre/mgs/autoMakefile lustre/ptlrpc/Makefile lustre/ptlrpc/autoMakefile +lustre/ptlrpc/gss/Makefile +lustre/ptlrpc/gss/autoMakefile lustre/quota/Makefile lustre/quota/autoMakefile lustre/scripts/Makefile lustre/scripts/version_tag.pl lustre/tests/Makefile lustre/utils/Makefile +lustre/utils/gss/Makefile ]) case $lb_target_os in darwin) diff --git a/lustre/kernel_patches/patches/export_symbols-2.6-rhel4.patch b/lustre/kernel_patches/patches/export_symbols-2.6-rhel4.patch index 0561e65..2a08192 100644 --- a/lustre/kernel_patches/patches/export_symbols-2.6-rhel4.patch +++ b/lustre/kernel_patches/patches/export_symbols-2.6-rhel4.patch @@ -79,3 +79,16 @@ Index: linux-2.6.9-5.0.3.EL/fs/dcache.c void d_genocide(struct dentry *root) { +Index: linux-2.6.12-rc6/net/sunrpc/sunrpc_syms.c +=================================================================== +--- linux-2.6.12.orig/net/sunrpc/sunrpc_syms.c 2005-12-14 23:20:39.000000000 -0700 ++++ linux-2.6.12/net/sunrpc/sunrpc_syms.c 2005-12-14 23:21:47.000000000 -0700 +@@ -58,6 +58,8 @@ EXPORT_SYMBOL(rpc_unlink); + EXPORT_SYMBOL(rpc_wake_up); + EXPORT_SYMBOL(rpc_queue_upcall); + EXPORT_SYMBOL(rpc_mkpipe); ++EXPORT_SYMBOL(rpc_mkdir); ++EXPORT_SYMBOL(rpc_rmdir); + + /* Client transport */ + EXPORT_SYMBOL(xprt_create_proto); diff --git a/lustre/kernel_patches/patches/export_symbols-2.6.12.patch b/lustre/kernel_patches/patches/export_symbols-2.6.12.patch index e21fcf4..6521703 100644 --- a/lustre/kernel_patches/patches/export_symbols-2.6.12.patch +++ b/lustre/kernel_patches/patches/export_symbols-2.6.12.patch @@ -62,3 +62,16 @@ Index: linux-2.6.12-rc6/fs/dcache.c void d_genocide(struct dentry *root) { +Index: linux-2.6.12-rc6/net/sunrpc/sunrpc_syms.c +=================================================================== +--- linux-2.6.12.orig/net/sunrpc/sunrpc_syms.c 2005-12-14 23:20:39.000000000 -0700 ++++ linux-2.6.12/net/sunrpc/sunrpc_syms.c 2005-12-14 23:21:47.000000000 -0700 +@@ -58,6 +58,8 @@ EXPORT_SYMBOL(rpc_unlink); + EXPORT_SYMBOL(rpc_wake_up); + EXPORT_SYMBOL(rpc_queue_upcall); + EXPORT_SYMBOL(rpc_mkpipe); ++EXPORT_SYMBOL(rpc_mkdir); ++EXPORT_SYMBOL(rpc_rmdir); + + /* Client transport */ + EXPORT_SYMBOL(xprt_create_proto); diff --git a/lustre/ptlrpc/Makefile.in b/lustre/ptlrpc/Makefile.in index 405c896..791f24e 100644 --- a/lustre/ptlrpc/Makefile.in +++ b/lustre/ptlrpc/Makefile.in @@ -17,6 +17,8 @@ ptlrpc_objs += sec.o sec_null.o sec_plain.o ptlrpc-objs := $(ldlm_objs) $(ptlrpc_objs) +@GSS_TRUE@subdir-m += gss + default: all ldlm_%.c: @LUSTRE@/ldlm/ldlm_%.c diff --git a/lustre/ptlrpc/gss/.cvsignore b/lustre/ptlrpc/gss/.cvsignore new file mode 100644 index 0000000..9acae98 --- /dev/null +++ b/lustre/ptlrpc/gss/.cvsignore @@ -0,0 +1,15 @@ +.Xrefs +config.log +config.status +configure +Makefile +.deps +tags +TAGS +.*.cmd +autoMakefile.in +autoMakefile +*.ko +*.mod.c +.*.flags +.depend diff --git a/lustre/ptlrpc/gss/Makefile.in b/lustre/ptlrpc/gss/Makefile.in new file mode 100644 index 0000000..3871c65 --- /dev/null +++ b/lustre/ptlrpc/gss/Makefile.in @@ -0,0 +1,9 @@ +MODULES := ptlrpc_gss + +ptlrpc_gss-objs := sec_gss.o gss_bulk.o gss_cli_upcall.o gss_svc_upcall.o \ + gss_rawobj.o lproc_gss.o gss_generic_token.o \ + gss_mech_switch.o gss_krb5_mech.o + +default: all + +@INCLUDE_RULES@ diff --git a/lustre/ptlrpc/gss/autoMakefile.am b/lustre/ptlrpc/gss/autoMakefile.am new file mode 100644 index 0000000..1c00f3df --- /dev/null +++ b/lustre/ptlrpc/gss/autoMakefile.am @@ -0,0 +1,15 @@ +# Copyright (C) 2006 Cluster File Systems, Inc. +# +# This code is issued under the GNU General Public License. +# See the file COPYING in this distribution + +if LIBLUSTRE +endif + +if MODULES +modulefs_DATA = ptlrpc_gss$(KMODEXT) +endif # MODULES + +DIST_SOURCES = $(ptlrpc_gss-objs:.o=.c) gss_api.h gss_asn1.h gss_err.h \ + gss_internal.h +MOSTLYCLEANFILES := @MOSTLYCLEANFILES@ diff --git a/lustre/ptlrpc/gss/gss_api.h b/lustre/ptlrpc/gss/gss_api.h new file mode 100644 index 0000000..b222036 --- /dev/null +++ b/lustre/ptlrpc/gss/gss_api.h @@ -0,0 +1,144 @@ +/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*- + * vim:expandtab:shiftwidth=8:tabstop=8: + * + * Modifications for Lustre + * Copyright 2004, Cluster File Systems, Inc. + * All rights reserved + * Author: Eric Mei + */ + +/* + * Somewhat simplified version of the gss api. + * + * Dug Song + * Andy Adamson + * Bruce Fields + * Copyright (c) 2000 The Regents of the University of Michigan + * + */ + +#ifndef __PTLRPC_GSS_GSS_API_H_ +#define __PTLRPC_GSS_GSS_API_H_ + +struct gss_api_mech; + +/* The mechanism-independent gss-api context: */ +struct gss_ctx { + struct gss_api_mech *mech_type; + void *internal_ctx_id; +}; + +#define GSS_C_NO_BUFFER ((rawobj_t) 0) +#define GSS_C_NO_CONTEXT ((struct gss_ctx *) 0) +#define GSS_C_NULL_OID ((rawobj_t) 0) + +/* + * gss-api prototypes; note that these are somewhat simplified versions of + * the prototypes specified in RFC 2744. + */ +__u32 lgss_import_sec_context( + rawobj_t *input_token, + struct gss_api_mech *mech, + struct gss_ctx **ctx_id); +__u32 lgss_copy_reverse_context( + struct gss_ctx *ctx_id, + struct gss_ctx **ctx_id_new); +__u32 lgss_inquire_context( + struct gss_ctx *ctx_id, + unsigned long *endtime); +__u32 lgss_get_mic( + struct gss_ctx *ctx_id, + int msgcnt, + rawobj_t *msgs, + rawobj_t *mic_token); +__u32 lgss_verify_mic( + struct gss_ctx *ctx_id, + int msgcnt, + rawobj_t *msgs, + rawobj_t *mic_token); +__u32 lgss_wrap( + struct gss_ctx *ctx_id, + rawobj_t *msg, + int msg_buflen, + rawobj_t *out_token); +__u32 lgss_unwrap( + struct gss_ctx *ctx_id, + rawobj_t *token, + rawobj_t *out_msg); +__u32 lgss_plain_encrypt( + struct gss_ctx *ctx_id, + int length, + void *in_buf, + void *out_buf); +__u32 lgss_delete_sec_context( + struct gss_ctx **ctx_id); + +struct subflavor_desc { + __u32 sf_subflavor; + __u32 sf_qop; + __u32 sf_service; + char *sf_name; +}; + +/* Each mechanism is described by the following struct: */ +struct gss_api_mech { + struct list_head gm_list; + struct module *gm_owner; + char *gm_name; + rawobj_t gm_oid; + atomic_t gm_count; + struct gss_api_ops *gm_ops; + int gm_sf_num; + struct subflavor_desc *gm_sfs; +}; + +/* and must provide the following operations: */ +struct gss_api_ops { + __u32 (*gss_import_sec_context)( + rawobj_t *input_token, + struct gss_ctx *ctx_id); + __u32 (*gss_copy_reverse_context)( + struct gss_ctx *ctx_id, + struct gss_ctx *ctx_id_new); + __u32 (*gss_inquire_context)( + struct gss_ctx *ctx_id, + unsigned long *endtime); + __u32 (*gss_get_mic)( + struct gss_ctx *ctx_id, + int msgcnt, + rawobj_t *msgs, + rawobj_t *mic_token); + __u32 (*gss_verify_mic)( + struct gss_ctx *ctx_id, + int msgcnt, + rawobj_t *msgs, + rawobj_t *mic_token); + __u32 (*gss_wrap)( + struct gss_ctx *ctx, + rawobj_t *msg, + int msg_buflen, + rawobj_t *out_token); + __u32 (*gss_unwrap)( + struct gss_ctx *ctx, + rawobj_t *token, + rawobj_t *out_msg); + __u32 (*gss_plain_encrypt)( + struct gss_ctx *ctx, + int length, + void *in_buf, + void *out_buf); + void (*gss_delete_sec_context)( + void *ctx_id); +}; + +int lgss_mech_register(struct gss_api_mech *mech); +void lgss_mech_unregister(struct gss_api_mech *mech); + +struct gss_api_mech * lgss_OID_to_mech(rawobj_t *oid); +struct gss_api_mech * lgss_name_to_mech(char *name); +struct gss_api_mech * lgss_subflavor_to_mech(__u32 subflavor); + +struct gss_api_mech * lgss_mech_get(struct gss_api_mech *mech); +void lgss_mech_put(struct gss_api_mech *mech); + +#endif /* __PTLRPC_GSS_GSS_API_H_ */ diff --git a/lustre/ptlrpc/gss/gss_asn1.h b/lustre/ptlrpc/gss/gss_asn1.h new file mode 100644 index 0000000..1148478 --- /dev/null +++ b/lustre/ptlrpc/gss/gss_asn1.h @@ -0,0 +1,85 @@ +/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*- + * vim:expandtab:shiftwidth=8:tabstop=8: + * + * Modifications for Lustre + * Copyright 2004, Cluster File Systems, Inc. + * All rights reserved + * Author: Eric Mei + */ + +/* + * minimal asn1 for generic encoding/decoding of gss tokens + * + * Adapted from MIT Kerberos 5-1.2.1 lib/include/krb5.h, + * lib/gssapi/krb5/gssapiP_krb5.h, and others + * + * Copyright (c) 2000 The Regents of the University of Michigan. + * All rights reserved. + * + * Andy Adamson + */ + +/* + * Copyright 1995 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. + * + */ + +#define SIZEOF_INT 4 + +/* from gssapi_err_generic.h */ +#define G_BAD_SERVICE_NAME (-2045022976L) +#define G_BAD_STRING_UID (-2045022975L) +#define G_NOUSER (-2045022974L) +#define G_VALIDATE_FAILED (-2045022973L) +#define G_BUFFER_ALLOC (-2045022972L) +#define G_BAD_MSG_CTX (-2045022971L) +#define G_WRONG_SIZE (-2045022970L) +#define G_BAD_USAGE (-2045022969L) +#define G_UNKNOWN_QOP (-2045022968L) +#define G_NO_HOSTNAME (-2045022967L) +#define G_BAD_HOSTNAME (-2045022966L) +#define G_WRONG_MECH (-2045022965L) +#define G_BAD_TOK_HEADER (-2045022964L) +#define G_BAD_DIRECTION (-2045022963L) +#define G_TOK_TRUNC (-2045022962L) +#define G_REFLECT (-2045022961L) +#define G_WRONG_TOKID (-2045022960L) + +#define g_OID_equal(o1,o2) \ + (((o1)->len == (o2)->len) && \ + (memcmp((o1)->data,(o2)->data,(int) (o1)->len) == 0)) + +__u32 g_verify_token_header(rawobj_t *mech, + int *body_size, + unsigned char **buf_in, + int toksize); + +__u32 g_get_mech_oid(rawobj_t *mech, + rawobj_t *in_buf); + +int g_token_size(rawobj_t *mech, + unsigned int body_size); + +void g_make_token_header(rawobj_t *mech, + int body_size, + unsigned char **buf); diff --git a/lustre/ptlrpc/gss/gss_cli_upcall.c b/lustre/ptlrpc/gss/gss_cli_upcall.c new file mode 100644 index 0000000..0a61dfe --- /dev/null +++ b/lustre/ptlrpc/gss/gss_cli_upcall.c @@ -0,0 +1,974 @@ +/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*- + * vim:expandtab:shiftwidth=8:tabstop=8: + * + * Copyright (C) 2006 Cluster File Systems, Inc. + * + * This file is part of the Lustre file system, http://www.lustre.org + * Lustre is a trademark of Cluster File Systems, Inc. + * + * You may have signed or agreed to another license before downloading + * this software. If so, you are bound by the terms and conditions + * of that agreement, and the following does not apply to you. See the + * LICENSE file included with this distribution for more information. + * + * If you did not agree to a different license, then this copy of Lustre + * is open source software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * In either case, Lustre is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * license text for more details. + * + */ + +#ifndef EXPORT_SYMTAB +# define EXPORT_SYMTAB +#endif +#define DEBUG_SUBSYSTEM S_SEC +#ifdef __KERNEL__ +#include +#include +#include +#include +#include +#include +/* for rpc_pipefs */ +struct rpc_clnt; +#include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "gss_err.h" +#include "gss_internal.h" +#include "gss_api.h" + +#define LUSTRE_PIPE_ROOT "/lustre" +#define LUSTRE_PIPE_KRB5 LUSTRE_PIPE_ROOT"/krb5" + +struct gss_upcall_msg_data { + __u32 gum_seq; + __u32 gum_uid; + __u32 gum_gid; + __u32 gum_svc; /* MDS/OSS... */ + __u64 gum_nid; /* peer NID */ + __u8 gum_obd[64]; /* client obd name */ +}; + +struct gss_upcall_msg { + struct rpc_pipe_msg gum_base; + atomic_t gum_refcount; + struct list_head gum_list; + __u32 gum_mechidx; + struct gss_sec *gum_gsec; + struct gss_cli_ctx *gum_gctx; + struct gss_upcall_msg_data gum_data; +}; + +static atomic_t upcall_seq = ATOMIC_INIT(0); + +static inline +__u32 upcall_get_sequence(void) +{ + return (__u32) atomic_inc_return(&upcall_seq); +} + +enum mech_idx_t { + MECH_KRB5 = 0, + MECH_MAX +}; + +static inline +__u32 mech_name2idx(const char *name) +{ + LASSERT(!strcmp(name, "krb5")); + return MECH_KRB5; +} + +/* pipefs dentries for each mechanisms */ +static struct dentry *de_pipes[MECH_MAX] = { NULL, }; +/* all upcall messgaes linked here */ +static struct list_head upcall_lists[MECH_MAX]; +/* and protected by this */ +static spinlock_t upcall_locks[MECH_MAX]; + +static inline +void upcall_list_lock(int idx) +{ + spin_lock(&upcall_locks[idx]); +} + +static inline +void upcall_list_unlock(int idx) +{ + spin_unlock(&upcall_locks[idx]); +} + +static +void upcall_msg_enlist(struct gss_upcall_msg *msg) +{ + __u32 idx = msg->gum_mechidx; + + upcall_list_lock(idx); + list_add(&msg->gum_list, &upcall_lists[idx]); + upcall_list_unlock(idx); +} + +static +void upcall_msg_delist(struct gss_upcall_msg *msg) +{ + __u32 idx = msg->gum_mechidx; + + upcall_list_lock(idx); + list_del_init(&msg->gum_list); + upcall_list_unlock(idx); +} + +/********************************************** + * rpc_pipe upcall helpers * + **********************************************/ +static +void gss_release_msg(struct gss_upcall_msg *gmsg) +{ + ENTRY; + LASSERT(atomic_read(&gmsg->gum_refcount) > 0); + + if (!atomic_dec_and_test(&gmsg->gum_refcount)) { + EXIT; + return; + } + + if (gmsg->gum_gctx) { + sptlrpc_ctx_wakeup(&gmsg->gum_gctx->gc_base); + sptlrpc_ctx_put(&gmsg->gum_gctx->gc_base, 1); + gmsg->gum_gctx = NULL; + } + + LASSERT(list_empty(&gmsg->gum_list)); + LASSERT(list_empty(&gmsg->gum_base.list)); + OBD_FREE(gmsg, sizeof(*gmsg)); + EXIT; +} + +static +void gss_unhash_msg_nolock(struct gss_upcall_msg *gmsg) +{ + __u32 idx = gmsg->gum_mechidx; + + LASSERT(idx < MECH_MAX); + LASSERT_SPIN_LOCKED(&upcall_locks[idx]); + + if (list_empty(&gmsg->gum_list)) + return; + + list_del_init(&gmsg->gum_list); + LASSERT(atomic_read(&gmsg->gum_refcount) > 1); + atomic_dec(&gmsg->gum_refcount); +} + +static +void gss_unhash_msg(struct gss_upcall_msg *gmsg) +{ + __u32 idx = gmsg->gum_mechidx; + + LASSERT(idx < MECH_MAX); + upcall_list_lock(idx); + gss_unhash_msg_nolock(gmsg); + upcall_list_unlock(idx); +} + +static +void gss_msg_fail_ctx(struct gss_upcall_msg *gmsg) +{ + if (gmsg->gum_gctx) { + struct ptlrpc_cli_ctx *ctx = &gmsg->gum_gctx->gc_base; + + LASSERT(atomic_read(&ctx->cc_refcount) > 0); + sptlrpc_ctx_expire(ctx); + set_bit(PTLRPC_CTX_ERROR_BIT, &ctx->cc_flags); + } +} + +static +struct gss_upcall_msg * gss_find_upcall(__u32 mechidx, __u32 seq) +{ + struct gss_upcall_msg *gmsg; + + upcall_list_lock(mechidx); + list_for_each_entry(gmsg, &upcall_lists[mechidx], gum_list) { + if (gmsg->gum_data.gum_seq != seq) + continue; + + LASSERT(atomic_read(&gmsg->gum_refcount) > 0); + LASSERT(gmsg->gum_mechidx == mechidx); + + atomic_inc(&gmsg->gum_refcount); + upcall_list_unlock(mechidx); + return gmsg; + } + upcall_list_unlock(mechidx); + return NULL; +} + +static +int simple_get_bytes(char **buf, __u32 *buflen, void *res, __u32 reslen) +{ + if (*buflen < reslen) { + CERROR("buflen %u < %u\n", *buflen, reslen); + return -EINVAL; + } + + memcpy(res, *buf, reslen); + *buf += reslen; + *buflen -= reslen; + return 0; +} + +/******************************************* + * rpc_pipe APIs * + *******************************************/ +static +ssize_t gss_pipe_upcall(struct file *filp, struct rpc_pipe_msg *msg, + char *dst, size_t buflen) +{ + char *data = (char *)msg->data + msg->copied; + ssize_t mlen = msg->len; + ssize_t left; + ENTRY; + + if (mlen > buflen) + mlen = buflen; + left = copy_to_user(dst, data, mlen); + if (left < 0) { + msg->errno = left; + RETURN(left); + } + mlen -= left; + msg->copied += mlen; + msg->errno = 0; + RETURN(mlen); +} + +static +ssize_t gss_pipe_downcall(struct file *filp, const char *src, size_t mlen) +{ + struct rpc_inode *rpci = RPC_I(filp->f_dentry->d_inode); + struct gss_upcall_msg *gss_msg; + struct ptlrpc_cli_ctx *ctx; + struct gss_cli_ctx *gctx = NULL; + char *buf, *data; + int datalen; + int timeout, rc; + __u32 mechidx, seq, gss_err; + ENTRY; + + mechidx = (__u32) rpci->private; + LASSERT(mechidx < MECH_MAX); + + OBD_ALLOC(buf, mlen); + if (!buf) + RETURN(-ENOMEM); + + if (copy_from_user(buf, src, mlen)) { + CERROR("failed copy user space data\n"); + GOTO(out_free, rc = -EFAULT); + } + data = buf; + datalen = mlen; + + /* data passed down format: + * - seq + * - timeout + * - gc_win / error + * - wire_ctx (rawobj) + * - mech_ctx (rawobj) + */ + if (simple_get_bytes(&data, &datalen, &seq, sizeof(seq))) { + CERROR("fail to get seq\n"); + GOTO(out_free, rc = -EFAULT); + } + + gss_msg = gss_find_upcall(mechidx, seq); + if (!gss_msg) { + CERROR("upcall %u has aborted earlier\n", seq); + GOTO(out_free, rc = -EINVAL); + } + + gss_unhash_msg(gss_msg); + gctx = gss_msg->gum_gctx; + LASSERT(gctx); + LASSERT(atomic_read(&gctx->gc_base.cc_refcount) > 0); + + /* timeout is not in use for now */ + if (simple_get_bytes(&data, &datalen, &timeout, sizeof(timeout))) + GOTO(out_msg, rc = -EFAULT); + + /* lgssd signal an error by gc_win == 0 */ + if (simple_get_bytes(&data, &datalen, &gctx->gc_win, + sizeof(gctx->gc_win))) + GOTO(out_msg, rc = -EFAULT); + + if (gctx->gc_win == 0) { + /* followed by: + * - rpc error + * - gss error + */ + if (simple_get_bytes(&data, &datalen, &rc, sizeof(rc))) + GOTO(out_msg, rc = -EFAULT); + if (simple_get_bytes(&data, &datalen, &gss_err,sizeof(gss_err))) + GOTO(out_msg, rc = -EFAULT); + + if (rc == 0 && gss_err == GSS_S_COMPLETE) { + CWARN("both rpc & gss error code not set\n"); + rc = -EPERM; + } + } else { + rawobj_t tmpobj; + + /* handle */ + if (rawobj_extract_local(&tmpobj, (__u32 **) &data, &datalen)) + GOTO(out_msg, rc = -EFAULT); + if (rawobj_dup(&gctx->gc_handle, &tmpobj)) + GOTO(out_msg, rc = -ENOMEM); + + /* mechctx */ + if (rawobj_extract_local(&tmpobj, (__u32 **) &data, &datalen)) + GOTO(out_msg, rc = -EFAULT); + gss_err = lgss_import_sec_context(&tmpobj, + gss_msg->gum_gsec->gs_mech, + &gctx->gc_mechctx); + rc = 0; + } + + if (likely(rc == 0 && gss_err == GSS_S_COMPLETE)) { + gss_cli_ctx_uptodate(gctx); + } else { + ctx = &gctx->gc_base; + sptlrpc_ctx_expire(ctx); + if (rc != -ERESTART || gss_err != GSS_S_COMPLETE) + set_bit(PTLRPC_CTX_ERROR_BIT, &ctx->cc_flags); + + CERROR("refresh ctx %p(uid %d) failed: %d/0x%08x: %s\n", + ctx, ctx->cc_vcred.vc_uid, rc, gss_err, + test_bit(PTLRPC_CTX_ERROR_BIT, &ctx->cc_flags) ? + "fatal error" : "non-fatal"); + } + + rc = mlen; + +out_msg: + gss_release_msg(gss_msg); + +out_free: + OBD_FREE(buf, mlen); + /* FIXME + * hack pipefs: always return asked length unless all following + * downcalls might be messed up. + */ + rc = mlen; + RETURN(rc); +} + +static +void gss_pipe_destroy_msg(struct rpc_pipe_msg *msg) +{ + struct gss_upcall_msg *gmsg; + struct gss_upcall_msg_data *gumd; + static cfs_time_t ratelimit = 0; + ENTRY; + + LASSERT(list_empty(&msg->list)); + + /* normally errno is >= 0 */ + if (msg->errno >= 0) { + EXIT; + return; + } + + gmsg = container_of(msg, struct gss_upcall_msg, gum_base); + gumd = &gmsg->gum_data; + LASSERT(atomic_read(&gmsg->gum_refcount) > 0); + + CERROR("failed msg %p (seq %u, uid %u, svc %u, nid %llx, obd %.*s): " + "errno %d\n", msg, gumd->gum_seq, gumd->gum_uid, gumd->gum_svc, + gumd->gum_nid, sizeof(gumd->gum_obd), gumd->gum_obd, msg->errno); + + atomic_inc(&gmsg->gum_refcount); + gss_unhash_msg(gmsg); + if (msg->errno == -ETIMEDOUT || msg->errno == -EPIPE) { + cfs_time_t now = cfs_time_current_sec(); + + if (cfs_time_after(now, ratelimit)) { + CWARN("upcall timed out, is lgssd running?\n"); + ratelimit = now + 15; + } + } + gss_msg_fail_ctx(gmsg); + gss_release_msg(gmsg); + EXIT; +} + +static +void gss_pipe_release(struct inode *inode) +{ + struct rpc_inode *rpci = RPC_I(inode); + __u32 idx; + ENTRY; + + idx = (__u32) rpci->private; + LASSERT(idx < MECH_MAX); + + upcall_list_lock(idx); + while (!list_empty(&upcall_lists[idx])) { + struct gss_upcall_msg *gmsg; + struct gss_upcall_msg_data *gumd; + + gmsg = list_entry(upcall_lists[idx].next, + struct gss_upcall_msg, gum_list); + gumd = &gmsg->gum_data; + LASSERT(list_empty(&gmsg->gum_base.list)); + + CERROR("failing remaining msg %p:seq %u, uid %u, svc %u, " + "nid %llx, obd %.*s\n", gmsg, + gumd->gum_seq, gumd->gum_uid, gumd->gum_svc, + gumd->gum_nid, sizeof(gumd->gum_obd), gumd->gum_obd); + + gmsg->gum_base.errno = -EPIPE; + atomic_inc(&gmsg->gum_refcount); + gss_unhash_msg_nolock(gmsg); + + gss_msg_fail_ctx(gmsg); + + upcall_list_unlock(idx); + gss_release_msg(gmsg); + upcall_list_lock(idx); + } + upcall_list_unlock(idx); + EXIT; +} + +static struct rpc_pipe_ops gss_upcall_ops = { + .upcall = gss_pipe_upcall, + .downcall = gss_pipe_downcall, + .destroy_msg = gss_pipe_destroy_msg, + .release_pipe = gss_pipe_release, +}; + + +/******************************************* + * upcall helper functions * + *******************************************/ + +static inline +__u32 import_to_gss_svc(struct obd_import *imp) +{ + const char *name = imp->imp_obd->obd_type->typ_name; + if (!strcmp(name, LUSTRE_MDC_NAME)) + return LUSTRE_GSS_TGT_MDS; + if (!strcmp(name, LUSTRE_OSC_NAME)) + return LUSTRE_GSS_TGT_OSS; + LBUG(); + return 0; +} + +int gss_ctx_refresh_pipefs(struct ptlrpc_cli_ctx *ctx) +{ + struct obd_import *imp; + struct gss_sec *gsec; + struct gss_upcall_msg *gmsg; + int rc = 0; + ENTRY; + + might_sleep(); + + LASSERT(ctx->cc_sec); + LASSERT(ctx->cc_sec->ps_import); + LASSERT(ctx->cc_sec->ps_import->imp_obd); + + imp = ctx->cc_sec->ps_import; + if (!imp->imp_connection) { + CERROR("import has no connection set\n"); + RETURN(-EINVAL); + } + + gsec = container_of(ctx->cc_sec, struct gss_sec, gs_base); + + OBD_ALLOC(gmsg, sizeof(*gmsg)); + if (!gmsg) + RETURN(-ENOMEM); + + /* initialize pipefs base msg */ + INIT_LIST_HEAD(&gmsg->gum_base.list); + gmsg->gum_base.data = &gmsg->gum_data; + gmsg->gum_base.len = sizeof(gmsg->gum_data); + gmsg->gum_base.copied = 0; + gmsg->gum_base.errno = 0; + + /* init upcall msg */ + atomic_set(&gmsg->gum_refcount, 1); + gmsg->gum_mechidx = mech_name2idx(gsec->gs_mech->gm_name); + gmsg->gum_gsec = gsec; + gmsg->gum_gctx = container_of(sptlrpc_ctx_get(ctx), + struct gss_cli_ctx, gc_base); + gmsg->gum_data.gum_seq = upcall_get_sequence(); + gmsg->gum_data.gum_uid = ctx->cc_vcred.vc_uid; + gmsg->gum_data.gum_gid = 0; /* not used for now */ + gmsg->gum_data.gum_svc = import_to_gss_svc(imp); + gmsg->gum_data.gum_nid = imp->imp_connection->c_peer.nid; + strncpy(gmsg->gum_data.gum_obd, imp->imp_obd->obd_name, + sizeof(gmsg->gum_data.gum_obd)); + + /* This only could happen when sysadmin set it dead/expired + * using lctl by force. + */ + smp_mb(); + if (ctx->cc_flags & PTLRPC_CTX_STATUS_MASK) { + CWARN("ctx %p(%u->%s) was set flags %lx unexpectedly\n", + ctx, ctx->cc_vcred.vc_uid, sec2target_str(ctx->cc_sec), + ctx->cc_flags); + + LASSERT(!(ctx->cc_flags & PTLRPC_CTX_UPTODATE)); + ctx->cc_flags |= PTLRPC_CTX_DEAD | PTLRPC_CTX_ERROR; + + rc = -EIO; + goto err_free; + } + + upcall_msg_enlist(gmsg); + + rc = rpc_queue_upcall(de_pipes[gmsg->gum_mechidx]->d_inode, + &gmsg->gum_base); + if (rc) { + CERROR("rpc_queue_upcall failed: %d\n", rc); + + upcall_msg_delist(gmsg); + goto err_free; + } + + RETURN(0); +err_free: + OBD_FREE(gmsg, sizeof(*gmsg)); + RETURN(rc); +} + +int gss_sec_upcall_init(struct gss_sec *gsec) +{ + return 0; +} + +void gss_sec_upcall_cleanup(struct gss_sec *gsec) +{ +} + +int gss_init_pipefs(void) +{ + struct dentry *de; + + /* pipe dir */ + de = rpc_mkdir(LUSTRE_PIPE_ROOT, NULL); + if (IS_ERR(de) && PTR_ERR(de) != -EEXIST) { + CERROR("Failed to create gss pipe dir: %ld\n", PTR_ERR(de)); + return PTR_ERR(de); + } + /* FIXME + * hack pipefs: dput will sometimes cause oops during module unload + * and lgssd close the pipe fds. + */ + //dput(de); + + /* krb5 mechanism */ + de = rpc_mkpipe(LUSTRE_PIPE_KRB5, (void *) MECH_KRB5, &gss_upcall_ops, + RPC_PIPE_WAIT_FOR_OPEN); + if (!de || IS_ERR(de)) { + CERROR("failed to make rpc_pipe %s: %ld\n", + LUSTRE_PIPE_KRB5, PTR_ERR(de)); + rpc_rmdir(LUSTRE_PIPE_ROOT); + return PTR_ERR(de); + } + + de_pipes[MECH_KRB5] = de; + INIT_LIST_HEAD(&upcall_lists[MECH_KRB5]); + upcall_locks[MECH_KRB5] = SPIN_LOCK_UNLOCKED; + + return 0; +} + +void gss_cleanup_pipefs(void) +{ + __u32 i; + + for (i = 0; i < MECH_MAX; i++) { + LASSERT(list_empty(&upcall_lists[i])); + /* FIXME + * hack pipefs, dput pipe dentry here might cause lgssd oops. + */ + //dput(de_pipes[i]); + de_pipes[i] = NULL; + } + + rpc_unlink(LUSTRE_PIPE_KRB5); + rpc_rmdir(LUSTRE_PIPE_ROOT); +} + +/********************************************** + * gss context init/fini helper * + **********************************************/ + +static +int ctx_init_pack_request(struct obd_import *imp, + struct ptlrpc_request *req, + int lustre_srv, + uid_t uid, gid_t gid, + long token_size, + char __user *token) +{ + struct lustre_msg *msg = req->rq_reqbuf; + struct gss_sec *gsec; + struct gss_header *ghdr; + struct ptlrpc_user_desc *pud; + __u32 *p, size, offset = 2; + rawobj_t obj; + + LASSERT(msg->lm_bufcount <= 4); + + /* gss hdr */ + ghdr = lustre_msg_buf(msg, 0, sizeof(*ghdr)); + ghdr->gh_version = PTLRPC_GSS_VERSION; + ghdr->gh_flags = 0; + ghdr->gh_proc = PTLRPC_GSS_PROC_INIT; + ghdr->gh_seq = 0; + ghdr->gh_svc = PTLRPC_GSS_SVC_NONE; + ghdr->gh_handle.len = 0; + + /* fix the user desc */ + if (SEC_FLAVOR_HAS_USER(req->rq_sec_flavor)) { + pud = lustre_msg_buf(msg, offset, sizeof(*pud)); + LASSERT(pud); + pud->pud_uid = pud->pud_fsuid = uid; + pud->pud_gid = pud->pud_fsgid = gid; + pud->pud_cap = 0; + pud->pud_ngroups = 0; + offset++; + } + + /* security payload */ + p = lustre_msg_buf(msg, offset, 0); + size = msg->lm_buflens[offset]; + + /* 1. lustre svc type */ + LASSERT(size > 4); + *p++ = cpu_to_le32(lustre_srv); + size -= 4; + + /* 2. target uuid */ + obj.len = strlen(imp->imp_obd->u.cli.cl_target_uuid.uuid) + 1; + obj.data = imp->imp_obd->u.cli.cl_target_uuid.uuid; + if (rawobj_serialize(&obj, &p, &size)) + LBUG(); + + /* 3. reverse context handle. actually only needed by root user, + * but we send it anyway. + */ + gsec = container_of(imp->imp_sec, struct gss_sec, gs_base); + obj.len = sizeof(gsec->gs_rvs_hdl); + obj.data = (__u8 *) &gsec->gs_rvs_hdl; + if (rawobj_serialize(&obj, &p, &size)) + LBUG(); + + /* 4. now the token */ + LASSERT(size >= (sizeof(__u32) + token_size)); + *p++ = cpu_to_le32(((__u32) token_size)); + if (copy_from_user(p, token, token_size)) { + CERROR("can't copy token\n"); + return -EFAULT; + } + size -= sizeof(__u32) + size_round4(token_size); + + req->rq_reqdata_len = lustre_shrink_msg(req->rq_reqbuf, offset, + msg->lm_buflens[offset] - size, 0); + return 0; +} + +static +int ctx_init_parse_reply(struct lustre_msg *msg, + char __user *outbuf, long outlen) +{ + struct gss_rep_header *ghdr; + __u32 obj_len, round_len; + __u32 status, effective = 0; + + if (msg->lm_bufcount != 3) { + CERROR("unexpected bufcount %u\n", msg->lm_bufcount); + return -EPROTO; + } + + ghdr = (struct gss_rep_header *) gss_swab_header(msg, 0); + if (ghdr == NULL) { + CERROR("unable to extract gss reply header\n"); + return -EPROTO; + } + + if (ghdr->gh_version != PTLRPC_GSS_VERSION) { + CERROR("invalid gss version %u\n", ghdr->gh_version); + return -EPROTO; + } + + if (outlen < (4 + 2) * 4 + size_round4(ghdr->gh_handle.len) + + size_round4(msg->lm_buflens[2])) { + CERROR("output buffer size %ld too small\n", outlen); + return -EFAULT; + } + + status = 0; + effective = 0; + + if (copy_to_user(outbuf, &status, 4)) + return -EFAULT; + outbuf += 4; + if (copy_to_user(outbuf, &ghdr->gh_major, 4)) + return -EFAULT; + outbuf += 4; + if (copy_to_user(outbuf, &ghdr->gh_minor, 4)) + return -EFAULT; + outbuf += 4; + if (copy_to_user(outbuf, &ghdr->gh_seqwin, 4)) + return -EFAULT; + outbuf += 4; + effective += 4 * 4; + + /* handle */ + obj_len = ghdr->gh_handle.len; + round_len = (obj_len + 3) & ~ 3; + if (copy_to_user(outbuf, &obj_len, 4)) + return -EFAULT; + outbuf += 4; + if (copy_to_user(outbuf, (char *) ghdr->gh_handle.data, round_len)) + return -EFAULT; + outbuf += round_len; + effective += 4 + round_len; + + /* out token */ + obj_len = msg->lm_buflens[2]; + round_len = (obj_len + 3) & ~ 3; + if (copy_to_user(outbuf, &obj_len, 4)) + return -EFAULT; + outbuf += 4; + if (copy_to_user(outbuf, lustre_msg_buf(msg, 2, 0), round_len)) + return -EFAULT; + outbuf += round_len; + effective += 4 + round_len; + + return effective; +} + +/* XXX move to where lgssd could see */ +struct lgssd_ioctl_param { + int version; /* in */ + char *uuid; /* in */ + int lustre_svc; /* in */ + uid_t uid; /* in */ + gid_t gid; /* in */ + long send_token_size;/* in */ + char *send_token; /* in */ + long reply_buf_size; /* in */ + char *reply_buf; /* in */ + long status; /* out */ + long reply_length; /* out */ +}; + +int gss_do_ctx_init_rpc(__user char *buffer, unsigned long count) +{ + struct obd_import *imp; + struct ptlrpc_request *req; + struct lgssd_ioctl_param param; + struct obd_device *obd; + char obdname[64]; + long lsize; + int lmsg_size = sizeof(struct ptlrpc_body); + int rc; + + if (count != sizeof(param)) { + CERROR("ioctl size %lu, expect %d, please check lgssd version\n", + count, sizeof(param)); + RETURN(-EINVAL); + } + if (copy_from_user(¶m, buffer, sizeof(param))) { + CERROR("failed copy data from lgssd\n"); + RETURN(-EFAULT); + } + + if (param.version != GSSD_INTERFACE_VERSION) { + CERROR("gssd interface version %d (expect %d)\n", + param.version, GSSD_INTERFACE_VERSION); + RETURN(-EINVAL); + } + + /* take name */ + if (strncpy_from_user(obdname, param.uuid, sizeof(obdname)) <= 0) { + CERROR("Invalid obdname pointer\n"); + RETURN(-EFAULT); + } + + obd = class_name2obd(obdname); + if (!obd) { + CERROR("no such obd %s\n", obdname); + RETURN(-EINVAL); + } + + imp = class_import_get(obd->u.cli.cl_import); + LASSERT(imp->imp_sec); + + /* force this import to use v2 msg */ + imp->imp_msg_magic = LUSTRE_MSG_MAGIC_V2; + + req = ptlrpc_prep_req(imp, LUSTRE_OBD_VERSION, SEC_CTX_INIT, + 1, &lmsg_size, NULL); + if (!req) { + param.status = -ENOMEM; + goto out_copy; + } + + /* get token */ + rc = ctx_init_pack_request(imp, req, + param.lustre_svc, + param.uid, param.gid, + param.send_token_size, + param.send_token); + if (rc) { + param.status = rc; + goto out_copy; + } + + req->rq_replen = lustre_msg_size_v2(1, &lmsg_size); + + rc = ptlrpc_queue_wait(req); + if (rc) { + /* If any _real_ denial be made, we expect server return + * error reply instead of simply drop request. So here + * all errors during networking just be treat as TIMEDOUT, + * caller might re-try negotiation again and again, leave + * recovery decisions to general ptlrpc layer. + */ + param.status = -ETIMEDOUT; + goto out_copy; + } + + lsize = ctx_init_parse_reply(req->rq_repbuf, + param.reply_buf, param.reply_buf_size); + if (lsize < 0) { + param.status = (int) lsize; + goto out_copy; + } + + param.status = 0; + param.reply_length = lsize; + +out_copy: + if (copy_to_user(buffer, ¶m, sizeof(param))) + rc = -EFAULT; + else + rc = 0; + + class_import_put(imp); + ptlrpc_req_finished(req); + RETURN(rc); +} + +int gss_do_ctx_fini_rpc(struct gss_cli_ctx *gctx) +{ + struct ptlrpc_cli_ctx *ctx = &gctx->gc_base; + struct obd_import *imp = ctx->cc_sec->ps_import; + struct ptlrpc_request *req; + struct ptlrpc_user_desc *pud; + int buflens = sizeof(struct ptlrpc_body); + int rc; + ENTRY; + + if (ctx->cc_sec->ps_flags & PTLRPC_SEC_FL_REVERSE) { + CWARN("ctx %p(%u) is reverse, don't send destroy rpc\n", + ctx, ctx->cc_vcred.vc_uid); + RETURN(0); + } + + if (test_bit(PTLRPC_CTX_ERROR_BIT, &ctx->cc_flags) || + !test_bit(PTLRPC_CTX_UPTODATE_BIT, &ctx->cc_flags)) { + CWARN("ctx %p(%u->%s) already dead, don't send destroy rpc\n", + ctx, ctx->cc_vcred.vc_uid, sec2target_str(ctx->cc_sec)); + RETURN(0); + } + + might_sleep(); + + CWARN("client destroy ctx %p(%u->%s)\n", + ctx, ctx->cc_vcred.vc_uid, sec2target_str(ctx->cc_sec)); + + /* context's refcount could be 0, steal one */ + atomic_inc(&ctx->cc_refcount); + + gctx->gc_proc = PTLRPC_GSS_PROC_DESTROY; + + req = ptlrpc_prep_req_pool(imp, LUSTRE_OBD_VERSION, SEC_CTX_FINI, + 1, &buflens, NULL, NULL, ctx); + if (!req) { + CWARN("ctx %p(%u): fail to prepare rpc, destroy locally\n", + ctx, ctx->cc_vcred.vc_uid); + GOTO(out_ref, rc = -ENOMEM); + } + + /* fix the user desc */ + if (SEC_FLAVOR_HAS_USER(req->rq_sec_flavor)) { + /* we rely the fact that this request is in AUTH mode, + * and user_desc at offset 2. + */ + pud = lustre_msg_buf(req->rq_reqbuf, 2, sizeof(*pud)); + LASSERT(pud); + pud->pud_uid = pud->pud_fsuid = ctx->cc_vcred.vc_uid; + pud->pud_gid = pud->pud_fsgid = ctx->cc_vcred.vc_gid; + pud->pud_cap = 0; + pud->pud_ngroups = 0; + } + + req->rq_replen = lustre_msg_size_v2(1, &buflens); + + rc = ptlrpc_queue_wait(req); + if (rc) { + CWARN("ctx %p(%u): rpc error %d, destroy locally\n", + ctx, ctx->cc_vcred.vc_uid, rc); + } + + ptlrpc_req_finished(req); +out_ref: + atomic_dec(&ctx->cc_refcount); + RETURN(rc); +} + +int __init gss_init_upcall(void) +{ + int rc; + + rc = gss_svc_init_upcall(); + if (rc) + return rc; + + rc = gss_init_pipefs(); + if (rc) + gss_svc_exit_upcall(); + + return rc; +} + +void __exit gss_exit_upcall(void) +{ + gss_svc_exit_upcall(); + gss_cleanup_pipefs(); +} diff --git a/lustre/ptlrpc/gss/gss_err.h b/lustre/ptlrpc/gss/gss_err.h new file mode 100644 index 0000000..a184501 --- /dev/null +++ b/lustre/ptlrpc/gss/gss_err.h @@ -0,0 +1,194 @@ +/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*- + * vim:expandtab:shiftwidth=8:tabstop=8: + * + * Modifications for Lustre + * Copyright 2004 - 2006, Cluster File Systems, Inc. + * All rights reserved + * Author: Eric Mei + */ + +/* + * Adapted from MIT Kerberos 5-1.2.1 include/gssapi/gssapi.h + * + * Copyright (c) 2002 The Regents of the University of Michigan. + * All rights reserved. + * + * Andy Adamson + */ + +/* + * Copyright 1993 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. + */ + +#ifndef __PTLRPC_GSS_GSS_ERR_H_ +#define __PTLRPC_GSS_GSS_ERR_H_ + +typedef unsigned int OM_uint32; + +/* + * Flag bits for context-level services. + */ +#define GSS_C_DELEG_FLAG (1) +#define GSS_C_MUTUAL_FLAG (2) +#define GSS_C_REPLAY_FLAG (4) +#define GSS_C_SEQUENCE_FLAG (8) +#define GSS_C_CONF_FLAG (16) +#define GSS_C_INTEG_FLAG (32) +#define GSS_C_ANON_FLAG (64) +#define GSS_C_PROT_READY_FLAG (128) +#define GSS_C_TRANS_FLAG (256) + +/* + * Credential usage options + */ +#define GSS_C_BOTH (0) +#define GSS_C_INITIATE (1) +#define GSS_C_ACCEPT (2) + +/* + * Status code types for gss_display_status + */ +#define GSS_C_GSS_CODE (1) +#define GSS_C_MECH_CODE (2) + + +/* + * Define the default Quality of Protection for per-message services. Note + * that an implementation that offers multiple levels of QOP may either reserve + * a value (for example zero, as assumed here) to mean "default protection", or + * alternatively may simply equate GSS_C_QOP_DEFAULT to a specific explicit + * QOP value. However a value of 0 should always be interpreted by a GSSAPI + * implementation as a request for the default protection level. + */ +#define GSS_C_QOP_DEFAULT (0) + +/* + * Expiration time of 2^32-1 seconds means infinite lifetime for a + * credential or security context + */ +#define GSS_C_INDEFINITE ((OM_uint32) 0xfffffffful) + + +/* Major status codes */ + +#define GSS_S_COMPLETE (0) + +/* + * Some "helper" definitions to make the status code macros obvious. + */ +#define GSS_C_CALLING_ERROR_OFFSET (24) +#define GSS_C_ROUTINE_ERROR_OFFSET (16) +#define GSS_C_SUPPLEMENTARY_OFFSET (0) +#define GSS_C_CALLING_ERROR_MASK ((OM_uint32) 0377ul) +#define GSS_C_ROUTINE_ERROR_MASK ((OM_uint32) 0377ul) +#define GSS_C_SUPPLEMENTARY_MASK ((OM_uint32) 0177777ul) + +/* + * The macros that test status codes for error conditions. Note that the + * GSS_ERROR() macro has changed slightly from the V1 GSSAPI so that it now + * evaluates its argument only once. + */ +#define GSS_CALLING_ERROR(x) \ + ((x) & (GSS_C_CALLING_ERROR_MASK << GSS_C_CALLING_ERROR_OFFSET)) +#define GSS_ROUTINE_ERROR(x) \ + ((x) & (GSS_C_ROUTINE_ERROR_MASK << GSS_C_ROUTINE_ERROR_OFFSET)) +#define GSS_SUPPLEMENTARY_INFO(x) \ + ((x) & (GSS_C_SUPPLEMENTARY_MASK << GSS_C_SUPPLEMENTARY_OFFSET)) +#define GSS_ERROR(x) \ + ((x) & ((GSS_C_CALLING_ERROR_MASK << GSS_C_CALLING_ERROR_OFFSET) | \ + (GSS_C_ROUTINE_ERROR_MASK << GSS_C_ROUTINE_ERROR_OFFSET))) + +/* + * Now the actual status code definitions + */ + +/* + * Calling errors: + */ +#define GSS_S_CALL_INACCESSIBLE_READ \ + (((OM_uint32) 1ul) << GSS_C_CALLING_ERROR_OFFSET) +#define GSS_S_CALL_INACCESSIBLE_WRITE \ + (((OM_uint32) 2ul) << GSS_C_CALLING_ERROR_OFFSET) +#define GSS_S_CALL_BAD_STRUCTURE \ + (((OM_uint32) 3ul) << GSS_C_CALLING_ERROR_OFFSET) + +/* + * Routine errors: + */ +#define GSS_S_BAD_MECH \ + (((OM_uint32) 1ul) << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_NAME \ + (((OM_uint32) 2ul) << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_NAMETYPE \ + (((OM_uint32) 3ul) << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_BINDINGS \ + (((OM_uint32) 4ul) << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_STATUS \ + (((OM_uint32) 5ul) << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_SIG \ + (((OM_uint32) 6ul) << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_NO_CRED \ + (((OM_uint32) 7ul) << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_NO_CONTEXT \ + (((OM_uint32) 8ul) << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_DEFECTIVE_TOKEN \ + (((OM_uint32) 9ul) << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_DEFECTIVE_CREDENTIAL \ + (((OM_uint32) 10ul) << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_CREDENTIALS_EXPIRED \ + (((OM_uint32) 11ul) << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_CONTEXT_EXPIRED \ + (((OM_uint32) 12ul) << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_FAILURE \ + (((OM_uint32) 13ul) << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_QOP \ + (((OM_uint32) 14ul) << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_UNAUTHORIZED \ + (((OM_uint32) 15ul) << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_UNAVAILABLE \ + (((OM_uint32) 16ul) << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_DUPLICATE_ELEMENT \ + (((OM_uint32) 17ul) << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_NAME_NOT_MN \ + (((OM_uint32) 18ul) << GSS_C_ROUTINE_ERROR_OFFSET) + +/* + * Supplementary info bits: + */ +#define GSS_S_CONTINUE_NEEDED (1 << (GSS_C_SUPPLEMENTARY_OFFSET + 0)) +#define GSS_S_DUPLICATE_TOKEN (1 << (GSS_C_SUPPLEMENTARY_OFFSET + 1)) +#define GSS_S_OLD_TOKEN (1 << (GSS_C_SUPPLEMENTARY_OFFSET + 2)) +#define GSS_S_UNSEQ_TOKEN (1 << (GSS_C_SUPPLEMENTARY_OFFSET + 3)) +#define GSS_S_GAP_TOKEN (1 << (GSS_C_SUPPLEMENTARY_OFFSET + 4)) + +/* XXXX these are not part of the GSSAPI C bindings! (but should be) */ + +#define GSS_CALLING_ERROR_FIELD(x) \ + (((x) >> GSS_C_CALLING_ERROR_OFFSET) & GSS_C_CALLING_ERROR_MASK) +#define GSS_ROUTINE_ERROR_FIELD(x) \ + (((x) >> GSS_C_ROUTINE_ERROR_OFFSET) & GSS_C_ROUTINE_ERROR_MASK) +#define GSS_SUPPLEMENTARY_INFO_FIELD(x) \ + (((x) >> GSS_C_SUPPLEMENTARY_OFFSET) & GSS_C_SUPPLEMENTARY_MASK) + +/* XXXX This is a necessary evil until the spec is fixed */ +#define GSS_S_CRED_UNAVAIL GSS_S_FAILURE + +#endif /* __PTLRPC_GSS_GSS_ERR_H_ */ diff --git a/lustre/ptlrpc/gss/gss_generic_token.c b/lustre/ptlrpc/gss/gss_generic_token.c new file mode 100644 index 0000000..6cb4028 --- /dev/null +++ b/lustre/ptlrpc/gss/gss_generic_token.c @@ -0,0 +1,290 @@ +/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*- + * vim:expandtab:shiftwidth=8:tabstop=8: + * + * Modifications for Lustre + * Copyright 2004 - 2006, Cluster File Systems, Inc. + * All rights reserved + * Author: Eric Mei + */ + +/* + * linux/net/sunrpc/gss_generic_token.c + * + * Adapted from MIT Kerberos 5-1.2.1 lib/gssapi/generic/util_token.c + * + * Copyright (c) 2000 The Regents of the University of Michigan. + * All rights reserved. + * + * Andy Adamson + */ + +/* + * Copyright 1993 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. + */ + +#ifndef EXPORT_SYMTAB +# define EXPORT_SYMTAB +#endif +#define DEBUG_SUBSYSTEM S_SEC +#ifdef __KERNEL__ +#include +#include +#include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "gss_err.h" +#include "gss_internal.h" +#include "gss_api.h" +#include "gss_krb5.h" +#include "gss_asn1.h" + + +/* TWRITE_STR from gssapiP_generic.h */ +#define TWRITE_STR(ptr, str, len) \ + memcpy((ptr), (char *) (str), (len)); \ + (ptr) += (len); + +/* XXXX this code currently makes the assumption that a mech oid will + never be longer than 127 bytes. This assumption is not inherent in + the interfaces, so the code can be fixed if the OSI namespace + balloons unexpectedly. */ + +/* Each token looks like this: + +0x60 tag for APPLICATION 0, SEQUENCE + (constructed, definite-length) + possible multiple bytes, need to parse/generate + 0x06 tag for OBJECT IDENTIFIER + compile-time constant string (assume 1 byte) + compile-time constant string + the ANY containing the application token + bytes 0,1 are the token type + bytes 2,n are the token data + +For the purposes of this abstraction, the token "header" consists of +the sequence tag and length octets, the mech OID DER encoding, and the +first two inner bytes, which indicate the token type. The token +"body" consists of everything else. + +*/ + +static +int der_length_size(int length) +{ + if (length < (1 << 7)) + return 1; + else if (length < (1 << 8)) + return 2; +#if (SIZEOF_INT == 2) + else + return 3; +#else + else if (length < (1 << 16)) + return 3; + else if (length < (1 << 24)) + return 4; + else + return 5; +#endif +} + +static +void der_write_length(unsigned char **buf, int length) +{ + if (length < (1 << 7)) { + *(*buf)++ = (unsigned char) length; + } else { + *(*buf)++ = (unsigned char) (der_length_size(length) + 127); +#if (SIZEOF_INT > 2) + if (length >= (1 << 24)) + *(*buf)++ = (unsigned char) (length >> 24); + if (length >= (1 << 16)) + *(*buf)++ = (unsigned char) ((length >> 16) & 0xff); +#endif + if (length >= (1 << 8)) + *(*buf)++ = (unsigned char) ((length >> 8) & 0xff); + *(*buf)++ = (unsigned char) (length & 0xff); + } +} + +/* + * returns decoded length, or < 0 on failure. Advances buf and + * decrements bufsize + */ +static +int der_read_length(unsigned char **buf, int *bufsize) +{ + unsigned char sf; + int ret; + + if (*bufsize < 1) + return -1; + sf = *(*buf)++; + (*bufsize)--; + if (sf & 0x80) { + if ((sf &= 0x7f) > ((*bufsize) - 1)) + return -1; + if (sf > SIZEOF_INT) + return -1; + ret = 0; + for (; sf; sf--) { + ret = (ret << 8) + (*(*buf)++); + (*bufsize)--; + } + } else { + ret = sf; + } + + return ret; +} + +/* + * returns the length of a token, given the mech oid and the body size + */ +int g_token_size(rawobj_t *mech, unsigned int body_size) +{ + /* set body_size to sequence contents size */ + body_size += 4 + (int) mech->len; /* NEED overflow check */ + return (1 + der_length_size(body_size) + body_size); +} + +/* + * fills in a buffer with the token header. The buffer is assumed to + * be the right size. buf is advanced past the token header + */ +void g_make_token_header(rawobj_t *mech, int body_size, unsigned char **buf) +{ + *(*buf)++ = 0x60; + der_write_length(buf, 4 + mech->len + body_size); + *(*buf)++ = 0x06; + *(*buf)++ = (unsigned char) mech->len; + TWRITE_STR(*buf, mech->data, ((int) mech->len)); +} + +/* + * Given a buffer containing a token, reads and verifies the token, + * leaving buf advanced past the token header, and setting body_size + * to the number of remaining bytes. Returns 0 on success, + * G_BAD_TOK_HEADER for a variety of errors, and G_WRONG_MECH if the + * mechanism in the token does not match the mech argument. buf and + * *body_size are left unmodified on error. + */ +__u32 g_verify_token_header(rawobj_t *mech, int *body_size, + unsigned char **buf_in, int toksize) +{ + unsigned char *buf = *buf_in; + int seqsize; + rawobj_t toid; + int ret = 0; + + if ((toksize -= 1) < 0) + return (G_BAD_TOK_HEADER); + if (*buf++ != 0x60) + return (G_BAD_TOK_HEADER); + + if ((seqsize = der_read_length(&buf, &toksize)) < 0) + return(G_BAD_TOK_HEADER); + + if (seqsize != toksize) + return (G_BAD_TOK_HEADER); + + if ((toksize -= 1) < 0) + return (G_BAD_TOK_HEADER); + if (*buf++ != 0x06) + return (G_BAD_TOK_HEADER); + + if ((toksize -= 1) < 0) + return (G_BAD_TOK_HEADER); + toid.len = *buf++; + + if ((toksize -= toid.len) < 0) + return (G_BAD_TOK_HEADER); + toid.data = buf; + buf += toid.len; + + if (!g_OID_equal(&toid, mech)) + ret = G_WRONG_MECH; + + /* G_WRONG_MECH is not returned immediately because it's more + * important to return G_BAD_TOK_HEADER if the token header is + * in fact bad + */ + if ((toksize -= 2) < 0) + return (G_BAD_TOK_HEADER); + + if (ret) + return (ret); + + if (!ret) { + *buf_in = buf; + *body_size = toksize; + } + + return (ret); +} + +/* + * Given a buffer containing a token, returns a copy of the mech oid in + * the parameter mech. + */ +__u32 g_get_mech_oid(rawobj_t *mech, rawobj_t *in_buf) +{ + unsigned char *buf = in_buf->data; + int len = in_buf->len; + int ret = 0; + int seqsize; + + if ((len -= 1) < 0) + return (G_BAD_TOK_HEADER); + if (*buf++ != 0x60) + return (G_BAD_TOK_HEADER); + + if ((seqsize = der_read_length(&buf, &len)) < 0) + return (G_BAD_TOK_HEADER); + + if ((len -= 1) < 0) + return (G_BAD_TOK_HEADER); + if (*buf++ != 0x06) + return (G_BAD_TOK_HEADER); + + if ((len -= 1) < 0) + return (G_BAD_TOK_HEADER); + mech->len = *buf++; + + if ((len -= mech->len) < 0) + return (G_BAD_TOK_HEADER); + OBD_ALLOC(mech->data, mech->len); + if (!mech->data) + return (G_BUFFER_ALLOC); + memcpy(mech->data, buf, mech->len); + + return ret; +} diff --git a/lustre/ptlrpc/gss/gss_internal.h b/lustre/ptlrpc/gss/gss_internal.h new file mode 100644 index 0000000..4d97d52 --- /dev/null +++ b/lustre/ptlrpc/gss/gss_internal.h @@ -0,0 +1,351 @@ +/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*- + * vim:expandtab:shiftwidth=8:tabstop=8: + * + * Modified from NFSv4 project for Lustre + * Copyright 2004 - 2006, Cluster File Systems, Inc. + * All rights reserved + * Author: Eric Mei + */ + +#ifndef __PTLRPC_GSS_GSS_INTERNAL_H_ +#define __PTLRPC_GSS_GSS_INTERNAL_H_ + +#include + +/* + * rawobj stuff + */ +typedef struct netobj_s { + __u32 len; + __u8 data[0]; +} netobj_t; + +#define NETOBJ_EMPTY ((netobj_t) { 0 }) + +typedef struct rawobj_s { + __u32 len; + __u8 *data; +} rawobj_t; + +#define RAWOBJ_EMPTY ((rawobj_t) { 0, NULL }) + +typedef struct rawobj_buf_s { + __u32 dataoff; + __u32 datalen; + __u32 buflen; + __u8 *buf; +} rawobj_buf_t; + +int rawobj_alloc(rawobj_t *obj, char *buf, int len); +void rawobj_free(rawobj_t *obj); +int rawobj_equal(rawobj_t *a, rawobj_t *b); +int rawobj_dup(rawobj_t *dest, rawobj_t *src); +int rawobj_serialize(rawobj_t *obj, __u32 **buf, __u32 *buflen); +int rawobj_extract(rawobj_t *obj, __u32 **buf, __u32 *buflen); +int rawobj_extract_alloc(rawobj_t *obj, __u32 **buf, __u32 *buflen); +int rawobj_extract_local(rawobj_t *obj, __u32 **buf, __u32 *buflen); +int rawobj_from_netobj(rawobj_t *rawobj, netobj_t *netobj); +int rawobj_from_netobj_alloc(rawobj_t *obj, netobj_t *netobj); + + +/* + * several timeout values. client refresh upcall timeout we using + * default in pipefs implemnetation. + */ +#define __TIMEOUT_DELTA (10) + +#define GSS_SECINIT_RPC_TIMEOUT \ + (obd_timeout < __TIMEOUT_DELTA ? \ + __TIMEOUT_DELTA : obd_timeout - __TIMEOUT_DELTA) + +#define GSS_SECFINI_RPC_TIMEOUT (__TIMEOUT_DELTA) +#define GSS_SECSVC_UPCALL_TIMEOUT (GSS_SECINIT_RPC_TIMEOUT) + +static inline +unsigned long gss_round_ctx_expiry(unsigned long expiry, + unsigned long sec_flags) +{ + if (sec_flags & PTLRPC_SEC_FL_REVERSE) + return expiry; + + if (get_seconds() + __TIMEOUT_DELTA <= expiry) + return expiry - __TIMEOUT_DELTA; + + return expiry; +} + +/* we try to force reconnect import 20m eariler than real expiry. + * kerberos 5 usually allow 5m time skew, but which is adjustable, + * so if we set krb5 to allow > 20m time skew, we have chance that + * server's reverse ctx expired but client still hasn't start to + * refresh it -- it's BAD. So here we actually put a limit on the + * enviroment of krb5 (or other authentication mechanism) + */ +#define GSS_MAX_TIME_SKEW (20 * 60) + +static inline +unsigned long gss_round_imp_reconnect(unsigned long expiry) +{ + unsigned long now = get_seconds(); + unsigned long nice = GSS_MAX_TIME_SKEW + __TIMEOUT_DELTA; + + while (nice && (now + nice >= expiry)) + nice = nice / 2; + + return (expiry - nice); +} + +/* + * Max encryption element in block cipher algorithms, most of which + * are 64 bits, here we choose 128 bits to be safe for future extension. + */ +#define GSS_MAX_CIPHER_BLOCK (16) + +/* + * XXX make it visible of kernel and lgssd/lsvcgssd + */ +#define GSSD_INTERFACE_VERSION (1) + +#define PTLRPC_GSS_VERSION (1) + + +enum ptlrpc_gss_proc { + PTLRPC_GSS_PROC_DATA = 0, + PTLRPC_GSS_PROC_INIT = 1, + PTLRPC_GSS_PROC_CONTINUE_INIT = 2, + PTLRPC_GSS_PROC_DESTROY = 3, + PTLRPC_GSS_PROC_ERR = 4, +}; + +enum ptlrpc_gss_svc { + PTLRPC_GSS_SVC_NONE = 1, + PTLRPC_GSS_SVC_INTEGRITY = 2, + PTLRPC_GSS_SVC_PRIVACY = 3, +}; + +enum ptlrpc_gss_tgt { + LUSTRE_GSS_TGT_MDS = 0, + LUSTRE_GSS_TGT_OSS = 1, +}; + +/* + * following 3 header must have the same size and offset + */ +struct gss_header { + __u32 gh_version; /* gss version */ + __u32 gh_flags; /* wrap flags */ + __u32 gh_proc; /* proc */ + __u32 gh_seq; /* sequence */ + __u32 gh_svc; /* service */ + __u32 gh_pad1; + __u32 gh_pad2; + __u32 gh_pad3; + netobj_t gh_handle; /* context handle */ +}; + +struct gss_rep_header { + __u32 gh_version; + __u32 gh_flags; + __u32 gh_proc; + __u32 gh_major; + __u32 gh_minor; + __u32 gh_seqwin; + __u32 gh_pad2; + __u32 gh_pad3; + netobj_t gh_handle; +}; + +struct gss_err_header { + __u32 gh_version; + __u32 gh_flags; + __u32 gh_proc; + __u32 gh_major; + __u32 gh_minor; + __u32 gh_pad1; + __u32 gh_pad2; + __u32 gh_pad3; + netobj_t gh_handle; +}; + +/* + * part of wire context information send from client which be saved and + * used later by server. + */ +struct gss_wire_ctx { + __u32 gw_proc; + __u32 gw_seq; + __u32 gw_svc; + rawobj_t gw_handle; +}; + +#define PTLRPC_GSS_MAX_HANDLE_SIZE (8) +#define PTLRPC_GSS_HEADER_SIZE (sizeof(struct gss_header) + \ + PTLRPC_GSS_MAX_HANDLE_SIZE) + + +#define GSS_SEQ_WIN (256) +#define GSS_SEQ_WIN_MAIN GSS_SEQ_WIN +#define GSS_SEQ_WIN_BACK (64) +#define GSS_SEQ_REPACK_THRESHOLD (GSS_SEQ_WIN_MAIN / 2) + +struct gss_svc_seq_data { + spinlock_t ssd_lock; + /* + * highest sequence number seen so far, for main and back window + */ + __u32 ssd_max_main; + __u32 ssd_max_back; + /* + * main and back window + * for i such that ssd_max - GSS_SEQ_WIN < i <= ssd_max, the i-th bit + * of ssd_win is nonzero iff sequence number i has been seen already. + */ + unsigned long ssd_win_main[GSS_SEQ_WIN_MAIN/BITS_PER_LONG]; + unsigned long ssd_win_back[GSS_SEQ_WIN_BACK/BITS_PER_LONG]; +}; + +struct gss_svc_ctx { + unsigned int gsc_usr_root:1, + gsc_usr_mds:1, + gsc_remote:1; + uid_t gsc_uid; + gid_t gsc_gid; + uid_t gsc_mapped_uid; + rawobj_t gsc_rvs_hdl; + struct gss_svc_seq_data gsc_seqdata; + struct gss_ctx *gsc_mechctx; +}; + +struct gss_svc_reqctx { + struct ptlrpc_svc_ctx src_base; + struct gss_wire_ctx src_wirectx; + struct gss_svc_ctx *src_ctx; + unsigned int src_init:1, + src_init_continue:1, + src_err_notify:1; + int src_reserve_len; +}; + +struct gss_cli_ctx { + struct ptlrpc_cli_ctx gc_base; + __u32 gc_flavor; + __u32 gc_proc; + __u32 gc_win; + atomic_t gc_seq; + rawobj_t gc_handle; + struct gss_ctx *gc_mechctx; +}; + +struct gss_sec { + struct ptlrpc_sec gs_base; + struct gss_api_mech *gs_mech; + spinlock_t gs_lock; + __u64 gs_rvs_hdl; +}; + +#define GSS_CTX_INIT_MAX_LEN (1024) + +/* + * This only guaranteed be enough for current krb5 des-cbc-crc . We might + * adjust this when new enc type or mech added in. + */ +#define GSS_PRIVBUF_PREFIX_LEN (32) +#define GSS_PRIVBUF_SUFFIX_LEN (32) + +static inline +struct gss_svc_reqctx *gss_svc_ctx2reqctx(struct ptlrpc_svc_ctx *ctx) +{ + LASSERT(ctx); + return container_of(ctx, struct gss_svc_reqctx, src_base); +} + +/* sec_gss.c */ +struct gss_header *gss_swab_header(struct lustre_msg *msg, int segment); +netobj_t *gss_swab_netobj(struct lustre_msg *msg, int segment); + +void gss_cli_ctx_uptodate(struct gss_cli_ctx *gctx); +int gss_pack_err_notify(struct ptlrpc_request *req, __u32 major, __u32 minor); +int gss_check_seq_num(struct gss_svc_seq_data *sd, __u32 seq_num, int set); + +/* gss_bulk.c */ +int gss_cli_ctx_wrap_bulk(struct ptlrpc_cli_ctx *ctx, + struct ptlrpc_request *req, + struct ptlrpc_bulk_desc *desc); +int gss_cli_ctx_unwrap_bulk(struct ptlrpc_cli_ctx *ctx, + struct ptlrpc_request *req, + struct ptlrpc_bulk_desc *desc); +int gss_svc_unwrap_bulk(struct ptlrpc_request *req, + struct ptlrpc_bulk_desc *desc); +int gss_svc_wrap_bulk(struct ptlrpc_request *req, + struct ptlrpc_bulk_desc *desc); + +/* gss_mech_switch.c */ +int init_kerberos_module(void); +void cleanup_kerberos_module(void); + +/* gss_generic_token.c */ +int g_token_size(rawobj_t *mech, unsigned int body_size); +void g_make_token_header(rawobj_t *mech, int body_size, unsigned char **buf); +__u32 g_verify_token_header(rawobj_t *mech, int *body_size, + unsigned char **buf_in, int toksize); + + +/* gss_upcall.c */ +int gss_do_ctx_init_rpc(char *buffer, unsigned long count); +int gss_do_ctx_fini_rpc(struct gss_cli_ctx *gctx); +int gss_ctx_refresh_pipefs(struct ptlrpc_cli_ctx *ctx); +int gss_sec_upcall_init(struct gss_sec *gsec); +void gss_sec_upcall_cleanup(struct gss_sec *gsec); +int __init gss_init_upcall(void); +void __exit gss_exit_upcall(void); + +/* gss_svc_upcall.c */ +__u64 gss_get_next_ctx_index(void); +int gss_svc_upcall_install_rvs_ctx(struct obd_import *imp, + struct gss_sec *gsec, + struct gss_cli_ctx *gctx); +int gss_svc_upcall_handle_init(struct ptlrpc_request *req, + struct gss_svc_reqctx *grctx, + struct gss_wire_ctx *gw, + struct obd_device *target, + __u32 lustre_svc, + rawobj_t *rvs_hdl, + rawobj_t *in_token); +struct gss_svc_ctx *gss_svc_upcall_get_ctx(struct ptlrpc_request *req, + struct gss_wire_ctx *gw); +void gss_svc_upcall_put_ctx(struct gss_svc_ctx *ctx); +void gss_svc_upcall_destroy_ctx(struct gss_svc_ctx *ctx); + +int __init gss_svc_init_upcall(void); +void __exit gss_svc_exit_upcall(void); + +/* lproc_gss.c */ +int gss_init_lproc(void); +void gss_exit_lproc(void); + +/* gss_krb5_mech.c */ +int __init init_kerberos_module(void); +void __exit cleanup_kerberos_module(void); + + +/* debug */ +static inline +void __dbg_memdump(char *name, void *ptr, int size) +{ + char *buf, *p = (char *) ptr; + int bufsize = size * 2 + 1, i; + + OBD_ALLOC(buf, bufsize); + if (!buf) { + printk("DUMP ERROR: can't alloc %d bytes\n", bufsize); + return; + } + + for (i = 0; i < size; i++) + sprintf(&buf[i+i], "%02x", (__u8) p[i]); + buf[size + size] = '\0'; + printk("DUMP %s@%p(%d): %s\n", name, ptr, size, buf); + OBD_FREE(buf, bufsize); +} + +#endif /* __PTLRPC_GSS_GSS_INTERNAL_H_ */ diff --git a/lustre/ptlrpc/gss/gss_krb5.h b/lustre/ptlrpc/gss/gss_krb5.h new file mode 100644 index 0000000..8cc4d44 --- /dev/null +++ b/lustre/ptlrpc/gss/gss_krb5.h @@ -0,0 +1,166 @@ +/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*- + * vim:expandtab:shiftwidth=8:tabstop=8: + * + * Modifications for Lustre + * Copyright 2004 - 2006, Cluster File Systems, Inc. + * All rights reserved + * Author: Eric Mei + */ + +/* + * linux/include/linux/sunrpc/gss_krb5_types.h + * + * Adapted from MIT Kerberos 5-1.2.1 lib/include/krb5.h, + * lib/gssapi/krb5/gssapiP_krb5.h, and others + * + * Copyright (c) 2000 The Regents of the University of Michigan. + * All rights reserved. + * + * Andy Adamson + * Bruce Fields + */ + +/* + * Copyright 1995 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. + * + */ + +#ifndef PTLRPC_GSS_KRB5_H +#define PTLRPC_GSS_KRB5_H + +extern spinlock_t krb5_seq_lock; + +/* + * RFC 4142 + */ + +#define KG_USAGE_ACCEPTOR_SEAL 22 +#define KG_USAGE_ACCEPTOR_SIGN 23 +#define KG_USAGE_INITIATOR_SEAL 24 +#define KG_USAGE_INITIATOR_SIGN 25 + +#define KG_TOK_MIC_MSG 0x0404 +#define KG_TOK_WRAP_MSG 0x0504 + +#define FLAG_SENDER_IS_ACCEPTOR 0x01 +#define FLAG_WRAP_CONFIDENTIAL 0x02 +#define FLAG_ACCEPTOR_SUBKEY 0x04 + +struct krb5_header { + __u16 kh_tok_id; /* token id */ + __u8 kh_flags; /* acceptor flags */ + __u8 kh_filler; /* 0xff */ + __u16 kh_ec; /* extra count */ + __u16 kh_rrc; /* right rotation count */ + __u64 kh_seq; /* sequence number */ + __u8 kh_cksum[0]; /* checksum */ +}; + +struct krb5_keyblock { + rawobj_t kb_key; + struct crypto_tfm *kb_tfm; +}; + +struct krb5_ctx { + unsigned int kc_initiate:1, + kc_cfx:1, + kc_seed_init:1, + kc_have_acceptor_subkey:1; + __s32 kc_endtime; + __u8 kc_seed[16]; + __u64 kc_seq_send; + __u64 kc_seq_recv; + __u32 kc_enctype; + struct krb5_keyblock kc_keye; /* encryption */ + struct krb5_keyblock kc_keyi; /* integrity */ + struct krb5_keyblock kc_keyc; /* checksum */ + rawobj_t kc_mech_used; +}; + +enum sgn_alg { + SGN_ALG_DES_MAC_MD5 = 0x0000, + SGN_ALG_MD2_5 = 0x0001, + SGN_ALG_DES_MAC = 0x0002, + SGN_ALG_3 = 0x0003, /* not published */ + SGN_ALG_HMAC_MD5 = 0x0011, /* microsoft w2k; no support */ + SGN_ALG_HMAC_SHA1_DES3_KD = 0x0004 +}; + +enum seal_alg { + SEAL_ALG_NONE = 0xffff, + SEAL_ALG_DES = 0x0000, + SEAL_ALG_1 = 0x0001, /* not published */ + SEAL_ALG_MICROSOFT_RC4 = 0x0010, /* microsoft w2k; no support */ + SEAL_ALG_DES3KD = 0x0002 +}; + +#define CKSUMTYPE_CRC32 0x0001 +#define CKSUMTYPE_RSA_MD4 0x0002 +#define CKSUMTYPE_RSA_MD4_DES 0x0003 +#define CKSUMTYPE_DESCBC 0x0004 +/* des-mac-k */ +/* rsa-md4-des-k */ +#define CKSUMTYPE_RSA_MD5 0x0007 +#define CKSUMTYPE_RSA_MD5_DES 0x0008 +#define CKSUMTYPE_NIST_SHA 0x0009 +#define CKSUMTYPE_HMAC_SHA1_DES3 0x000c +#define CKSUMTYPE_HMAC_SHA1_96_AES128 0x000f +#define CKSUMTYPE_HMAC_SHA1_96_AES256 0x0010 +#define CKSUMTYPE_HMAC_MD5_ARCFOUR -138 + +/* from gssapi_err_krb5.h */ +#define KG_CCACHE_NOMATCH (39756032L) +#define KG_KEYTAB_NOMATCH (39756033L) +#define KG_TGT_MISSING (39756034L) +#define KG_NO_SUBKEY (39756035L) +#define KG_CONTEXT_ESTABLISHED (39756036L) +#define KG_BAD_SIGN_TYPE (39756037L) +#define KG_BAD_LENGTH (39756038L) +#define KG_CTX_INCOMPLETE (39756039L) +#define KG_CONTEXT (39756040L) +#define KG_CRED (39756041L) +#define KG_ENC_DESC (39756042L) +#define KG_BAD_SEQ (39756043L) +#define KG_EMPTY_CCACHE (39756044L) +#define KG_NO_CTYPES (39756045L) + +/* per Kerberos v5 protocol spec crypto types from the wire. + * these get mapped to linux kernel crypto routines. + */ +#define ENCTYPE_NULL 0x0000 +#define ENCTYPE_DES_CBC_CRC 0x0001 /* DES cbc mode with CRC-32 */ +#define ENCTYPE_DES_CBC_MD4 0x0002 /* DES cbc mode with RSA-MD4 */ +#define ENCTYPE_DES_CBC_MD5 0x0003 /* DES cbc mode with RSA-MD5 */ +#define ENCTYPE_DES_CBC_RAW 0x0004 /* DES cbc mode raw */ +/* XXX deprecated? */ +#define ENCTYPE_DES3_CBC_SHA 0x0005 /* DES-3 cbc mode with NIST-SHA */ +#define ENCTYPE_DES3_CBC_RAW 0x0006 /* DES-3 cbc mode raw */ +#define ENCTYPE_DES_HMAC_SHA1 0x0008 +#define ENCTYPE_DES3_CBC_SHA1 0x0010 +#define ENCTYPE_AES128_CTS_HMAC_SHA1_96 0x0011 +#define ENCTYPE_AES256_CTS_HMAC_SHA1_96 0x0012 +#define ENCTYPE_ARCFOUR_HMAC 0x0017 +#define ENCTYPE_ARCFOUR_HMAC_EXP 0x0018 +#define ENCTYPE_UNKNOWN 0x01ff + +#endif /* PTLRPC_GSS_KRB5_H */ diff --git a/lustre/ptlrpc/gss/gss_krb5_mech.c b/lustre/ptlrpc/gss/gss_krb5_mech.c new file mode 100644 index 0000000..8b69a05 --- /dev/null +++ b/lustre/ptlrpc/gss/gss_krb5_mech.c @@ -0,0 +1,1092 @@ +/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*- + * vim:expandtab:shiftwidth=8:tabstop=8: + * + * Modifications for Lustre + * Copyright 2004 - 2006, Cluster File Systems, Inc. + * All rights reserved + * Author: Eric Mei + */ + +/* + * linux/net/sunrpc/gss_krb5_mech.c + * + * Copyright (c) 2001 The Regents of the University of Michigan. + * All rights reserved. + * + * Andy Adamson + * J. Bruce Fields + * + * 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 EXPORT_SYMTAB +# define EXPORT_SYMTAB +#endif +#define DEBUG_SUBSYSTEM S_SEC +#ifdef __KERNEL__ +#include +#include +#include +#include +#include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "gss_err.h" +#include "gss_internal.h" +#include "gss_api.h" +#include "gss_asn1.h" +#include "gss_krb5.h" + +spinlock_t krb5_seq_lock = SPIN_LOCK_UNLOCKED; + +struct krb5_enctype { + int ke_hash_size; + char *ke_hash_name; + char *ke_enc_name; + int ke_enc_mode; + unsigned int ke_hash_hmac:1; +}; + +/* + * NOTE: for aes128-cts and aes256-cts, MIT implementation use CTS + * encryption mode while we CBC with padding, because we already be able + * to handle trailling bytes, and dosen't hurt security and simpler. + */ +static struct krb5_enctype enctypes[] = { + [ENCTYPE_DES_CBC_RAW] = { /* des-cbc-md5 */ + 16, + "md5", + "des", + CRYPTO_TFM_MODE_CBC, + 0, + }, + [ENCTYPE_DES3_CBC_RAW] = { /* des3-hmac-sha1 */ + 20, + "sha1", + "des3_ede", + CRYPTO_TFM_MODE_CBC, + 1, + }, + [ENCTYPE_AES128_CTS_HMAC_SHA1_96] = { /* aes128-cts */ + 12, + "sha1", + "aes", + CRYPTO_TFM_MODE_CBC, + 1, + }, + [ENCTYPE_AES256_CTS_HMAC_SHA1_96] = { /* aes256-cts */ + 12, + "sha1", + "aes", + CRYPTO_TFM_MODE_CBC, + 1, + }, +}; + +#define MAX_ENCTYPES sizeof(enctypes)/sizeof(struct krb5_enctype) + +static +int keyblock_init(struct krb5_keyblock *kb, char *alg_name, int alg_mode) +{ + kb->kb_tfm = crypto_alloc_tfm(alg_name, alg_mode); + if (kb->kb_tfm == NULL) { + CERROR("failed to alloc tfm: %s, mode %d\n", + alg_name, alg_mode); + return -1; + } + + if (crypto_cipher_setkey(kb->kb_tfm, kb->kb_key.data, kb->kb_key.len)) { + CERROR("failed to set %s key, len %d\n", + alg_name, kb->kb_key.len); + return -1; + } + + return 0; +} + +static +int krb5_init_keys(struct krb5_ctx *kctx) +{ + struct krb5_enctype *ke; + + if (kctx->kc_enctype >= MAX_ENCTYPES || + enctypes[kctx->kc_enctype].ke_hash_size == 0) { + CERROR("unsupported enctype %x\n", kctx->kc_enctype); + return -1; + } + + ke = &enctypes[kctx->kc_enctype]; + + if (keyblock_init(&kctx->kc_keye, ke->ke_enc_name, ke->ke_enc_mode)) + return -1; + if (ke->ke_hash_hmac == 0 && + keyblock_init(&kctx->kc_keyi, ke->ke_enc_name, ke->ke_enc_mode)) + return -1; + if (ke->ke_hash_hmac == 0 && + keyblock_init(&kctx->kc_keyc, ke->ke_enc_name, ke->ke_enc_mode)) + return -1; + + return 0; +} + +static +void keyblock_free(struct krb5_keyblock *kb) +{ + rawobj_free(&kb->kb_key); + if (kb->kb_tfm) + crypto_free_tfm(kb->kb_tfm); +} + +static +int keyblock_dup(struct krb5_keyblock *new, struct krb5_keyblock *kb) +{ + return rawobj_dup(&new->kb_key, &kb->kb_key); +} + +static +int get_bytes(char **ptr, const char *end, void *res, int len) +{ + char *p, *q; + p = *ptr; + q = p + len; + if (q > end || q < p) + return -1; + memcpy(res, p, len); + *ptr = q; + return 0; +} + +static +int get_rawobj(char **ptr, const char *end, rawobj_t *res) +{ + char *p, *q; + + p = *ptr; + if (get_bytes(&p, end, &res->len, sizeof(res->len))) + return -1; + + q = p + res->len; + if (q > end || q < p) + return -1; + + OBD_ALLOC(res->data, res->len); + if (!res->data) + return -1; + + memcpy(res->data, p, res->len); + *ptr = q; + return 0; +} + +static +int get_keyblock(char **ptr, const char *end, + struct krb5_keyblock *kb, __u32 keysize) +{ + char *buf; + + OBD_ALLOC(buf, keysize); + if (buf == NULL) + return -1; + + if (get_bytes(ptr, end, buf, keysize)) { + OBD_FREE(buf, keysize); + return -1; + } + + kb->kb_key.len = keysize; + kb->kb_key.data = buf; + return 0; +} + +static +void delete_context_kerberos(struct krb5_ctx *kctx) +{ + rawobj_free(&kctx->kc_mech_used); + + keyblock_free(&kctx->kc_keye); + keyblock_free(&kctx->kc_keyi); + keyblock_free(&kctx->kc_keyc); +} + +static +__u32 import_context_rfc1964(struct krb5_ctx *kctx, char *p, char *end) +{ + unsigned int tmp_uint, keysize; + + /* seed_init flag */ + if (get_bytes(&p, end, &tmp_uint, sizeof(tmp_uint))) + goto out_err; + kctx->kc_seed_init = (tmp_uint != 0); + + /* seed */ + if (get_bytes(&p, end, kctx->kc_seed, sizeof(kctx->kc_seed))) + goto out_err; + + /* sign/seal algorithm, not really used now */ + if (get_bytes(&p, end, &tmp_uint, sizeof(tmp_uint)) || + get_bytes(&p, end, &tmp_uint, sizeof(tmp_uint))) + goto out_err; + + /* end time */ + if (get_bytes(&p, end, &kctx->kc_endtime, sizeof(kctx->kc_endtime))) + goto out_err; + + /* seq send */ + if (get_bytes(&p, end, &tmp_uint, sizeof(tmp_uint))) + goto out_err; + kctx->kc_seq_send = tmp_uint; + + /* mech oid */ + if (get_rawobj(&p, end, &kctx->kc_mech_used)) + goto out_err; + + /* old style enc/seq keys in format: + * - enctype (u32) + * - keysize (u32) + * - keydata + * we decompose them to fit into the new context + */ + + /* enc key */ + if (get_bytes(&p, end, &kctx->kc_enctype, sizeof(kctx->kc_enctype))) + goto out_err; + + if (get_bytes(&p, end, &keysize, sizeof(keysize))) + goto out_err; + + if (get_keyblock(&p, end, &kctx->kc_keye, keysize)) + goto out_err; + + /* seq key */ + if (get_bytes(&p, end, &tmp_uint, sizeof(tmp_uint)) || + tmp_uint != kctx->kc_enctype) + goto out_err; + + if (get_bytes(&p, end, &tmp_uint, sizeof(tmp_uint)) || + tmp_uint != keysize) + goto out_err; + + if (get_keyblock(&p, end, &kctx->kc_keyc, keysize)) + goto out_err; + + /* old style fallback */ + if (keyblock_dup(&kctx->kc_keyi, &kctx->kc_keyc)) + goto out_err; + + if (p != end) + goto out_err; + + CDEBUG(D_SEC, "succesfully imported rfc1964 context\n"); + return 0; +out_err: + return GSS_S_FAILURE; +} + +/* Flags for version 2 context flags */ +#define KRB5_CTX_FLAG_INITIATOR 0x00000001 +#define KRB5_CTX_FLAG_CFX 0x00000002 +#define KRB5_CTX_FLAG_ACCEPTOR_SUBKEY 0x00000004 + +static +__u32 import_context_v2(struct krb5_ctx *kctx, char *p, char *end) +{ + unsigned int tmp_uint, keysize; + + /* end time */ + if (get_bytes(&p, end, &kctx->kc_endtime, sizeof(kctx->kc_endtime))) + goto out_err; + + /* flags */ + if (get_bytes(&p, end, &tmp_uint, sizeof(tmp_uint))) + goto out_err; + + if (tmp_uint & KRB5_CTX_FLAG_INITIATOR) + kctx->kc_initiate = 1; + if (tmp_uint & KRB5_CTX_FLAG_CFX) + kctx->kc_cfx = 1; + if (tmp_uint & KRB5_CTX_FLAG_ACCEPTOR_SUBKEY) + kctx->kc_have_acceptor_subkey = 1; + + /* seq send */ + if (get_bytes(&p, end, &kctx->kc_seq_send, sizeof(kctx->kc_seq_send))) + goto out_err; + + /* enctype */ + if (get_bytes(&p, end, &kctx->kc_enctype, sizeof(kctx->kc_enctype))) + goto out_err; + + /* size of each key */ + if (get_bytes(&p, end, &keysize, sizeof(keysize))) + goto out_err; + + /* number of keys - should always be 3 */ + if (get_bytes(&p, end, &tmp_uint, sizeof(tmp_uint))) + goto out_err; + + if (tmp_uint != 3) { + CERROR("Invalid number of keys: %u\n", tmp_uint); + goto out_err; + } + + /* ke */ + if (get_keyblock(&p, end, &kctx->kc_keye, keysize)) + goto out_err; + /* ki */ + if (get_keyblock(&p, end, &kctx->kc_keyi, keysize)) + goto out_err; + /* ki */ + if (get_keyblock(&p, end, &kctx->kc_keyc, keysize)) + goto out_err; + + CDEBUG(D_SEC, "succesfully imported v2 context\n"); + return 0; +out_err: + return GSS_S_FAILURE; +} + +/* + * The whole purpose here is trying to keep user level gss context parsing + * from nfs-utils unchanged as possible as we can, they are not quite mature + * yet, and many stuff still not clear, like heimdal etc. + */ +static +__u32 gss_import_sec_context_kerberos(rawobj_t *inbuf, + struct gss_ctx *gctx) +{ + struct krb5_ctx *kctx; + char *p = (char *) inbuf->data; + char *end = (char *) (inbuf->data + inbuf->len); + unsigned int tmp_uint, rc; + + if (get_bytes(&p, end, &tmp_uint, sizeof(tmp_uint))) { + CERROR("Fail to read version\n"); + return GSS_S_FAILURE; + } + + /* only support 0, 1 for the moment */ + if (tmp_uint > 2) { + CERROR("Invalid version %u\n", tmp_uint); + return GSS_S_FAILURE; + } + + OBD_ALLOC(kctx, sizeof(*kctx)); + if (!kctx) + return GSS_S_FAILURE; + + if (tmp_uint == 0 || tmp_uint == 1) { + kctx->kc_initiate = tmp_uint; + rc = import_context_rfc1964(kctx, p, end); + } else { + rc = import_context_v2(kctx, p, end); + } + + if (rc == 0) + rc = krb5_init_keys(kctx); + + if (rc) { + delete_context_kerberos(kctx); + OBD_FREE(kctx, sizeof(*kctx)); + + return GSS_S_FAILURE; + } + + gctx->internal_ctx_id = kctx; + return GSS_S_COMPLETE; +} + +static +__u32 gss_copy_reverse_context_kerberos(struct gss_ctx *gctx, + struct gss_ctx *gctx_new) +{ + struct krb5_ctx *kctx = gctx->internal_ctx_id; + struct krb5_ctx *knew; + + OBD_ALLOC(knew, sizeof(*knew)); + if (!knew) + return GSS_S_FAILURE; + + knew->kc_initiate = kctx->kc_initiate ? 0 : 1; + knew->kc_seed_init = kctx->kc_seed_init; + memcpy(knew->kc_seed, kctx->kc_seed, sizeof(kctx->kc_seed)); + knew->kc_endtime = kctx->kc_endtime; + knew->kc_seq_send = kctx->kc_seq_recv; + knew->kc_seq_recv = kctx->kc_seq_send; + knew->kc_enctype = kctx->kc_enctype; + + if (rawobj_dup(&knew->kc_mech_used, &kctx->kc_mech_used)) + goto out_err; + + if (keyblock_dup(&knew->kc_keye, &kctx->kc_keye)) + goto out_err; + if (keyblock_dup(&knew->kc_keyi, &kctx->kc_keyi)) + goto out_err; + if (keyblock_dup(&knew->kc_keyc, &kctx->kc_keyc)) + goto out_err; + if (krb5_init_keys(knew)) + goto out_err; + + gctx_new->internal_ctx_id = knew; + CDEBUG(D_SEC, "succesfully copied reverse context\n"); + return GSS_S_COMPLETE; + +out_err: + delete_context_kerberos(knew); + OBD_FREE(knew, sizeof(*knew)); + return GSS_S_FAILURE; +} + +static +__u32 gss_inquire_context_kerberos(struct gss_ctx *gctx, + unsigned long *endtime) +{ + struct krb5_ctx *kctx = gctx->internal_ctx_id; + + *endtime = (unsigned long) ((__u32) kctx->kc_endtime); + return GSS_S_COMPLETE; +} + +static +void gss_delete_sec_context_kerberos(void *internal_ctx) +{ + struct krb5_ctx *kctx = internal_ctx; + + delete_context_kerberos(kctx); + OBD_FREE(kctx, sizeof(*kctx)); +} + +static +void buf_to_sg(struct scatterlist *sg, char *ptr, int len) +{ + sg->page = virt_to_page(ptr); + sg->offset = offset_in_page(ptr); + sg->length = len; +} + +static +__u32 krb5_encrypt(struct crypto_tfm *tfm, + int decrypt, + void * iv, + void * in, + void * out, + int length) +{ + struct scatterlist sg; + __u8 local_iv[16] = {0}; + __u32 ret = -EINVAL; + + LASSERT(tfm); + + if (length % crypto_tfm_alg_blocksize(tfm) != 0) { + CERROR("output length %d mismatch blocksize %d\n", + length, crypto_tfm_alg_blocksize(tfm)); + goto out; + } + + if (crypto_tfm_alg_ivsize(tfm) > 16) { + CERROR("iv size too large %d\n", crypto_tfm_alg_ivsize(tfm)); + goto out; + } + + if (iv) + memcpy(local_iv, iv, crypto_tfm_alg_ivsize(tfm)); + + memcpy(out, in, length); + buf_to_sg(&sg, out, length); + + if (decrypt) + ret = crypto_cipher_decrypt_iv(tfm, &sg, &sg, length, local_iv); + else + ret = crypto_cipher_encrypt_iv(tfm, &sg, &sg, length, local_iv); + +out: + return(ret); +} + +static inline +int krb5_digest_hmac(struct crypto_tfm *tfm, + rawobj_t *key, + struct krb5_header *khdr, + int msgcnt, rawobj_t *msgs, + rawobj_t *cksum) +{ + struct scatterlist sg[1]; + __u32 keylen = key->len, i; + + crypto_hmac_init(tfm, key->data, &keylen); + + for (i = 0; i < msgcnt; i++) { + if (msgs[i].len == 0) + continue; + buf_to_sg(sg, (char *) msgs[i].data, msgs[i].len); + crypto_hmac_update(tfm, sg, 1); + } + + buf_to_sg(sg, (char *) khdr, sizeof(*khdr)); + crypto_hmac_update(tfm, sg, 1); + + crypto_hmac_final(tfm, key->data, &keylen, cksum->data); + return 0; +} + +static inline +int krb5_digest_norm(struct crypto_tfm *tfm, + struct krb5_keyblock *kb, + struct krb5_header *khdr, + int msgcnt, rawobj_t *msgs, + rawobj_t *cksum) +{ + struct scatterlist sg[1]; + int i; + + LASSERT(kb->kb_tfm); + + crypto_digest_init(tfm); + + for (i = 0; i < msgcnt; i++) { + if (msgs[i].len == 0) + continue; + buf_to_sg(sg, (char *) msgs[i].data, msgs[i].len); + crypto_digest_update(tfm, sg, 1); + } + + buf_to_sg(sg, (char *) khdr, sizeof(*khdr)); + crypto_digest_update(tfm, sg, 1); + + crypto_digest_final(tfm, cksum->data); + + return krb5_encrypt(kb->kb_tfm, 0, NULL, cksum->data, + cksum->data, cksum->len); +} + +/* + * compute (keyed/keyless) checksum against the plain text which appended + * with krb5 wire token header. + */ +static +__s32 krb5_make_checksum(__u32 enctype, + struct krb5_keyblock *kb, + struct krb5_header *khdr, + int msgcnt, rawobj_t *msgs, + rawobj_t *cksum) +{ + struct krb5_enctype *ke = &enctypes[enctype]; + struct crypto_tfm *tfm; + __u32 code = GSS_S_FAILURE; + int rc; + + if (!(tfm = crypto_alloc_tfm(ke->ke_hash_name, 0))) { + CERROR("failed to alloc TFM: %s\n", ke->ke_hash_name); + return GSS_S_FAILURE; + } + + cksum->len = crypto_tfm_alg_digestsize(tfm); + OBD_ALLOC(cksum->data, cksum->len); + if (!cksum->data) { + cksum->len = 0; + goto out_tfm; + } + + if (ke->ke_hash_hmac) + rc = krb5_digest_hmac(tfm, &kb->kb_key, + khdr, msgcnt, msgs, cksum); + else + rc = krb5_digest_norm(tfm, kb, + khdr, msgcnt, msgs, cksum); + + if (rc == 0) + code = GSS_S_COMPLETE; +out_tfm: + crypto_free_tfm(tfm); + return code; +} + +static +__u32 gss_get_mic_kerberos(struct gss_ctx *gctx, + int msgcnt, + rawobj_t *msgs, + rawobj_t *token) +{ + struct krb5_ctx *kctx = gctx->internal_ctx_id; + struct krb5_enctype *ke = &enctypes[kctx->kc_enctype]; + struct krb5_header *khdr; + unsigned char acceptor_flag; + rawobj_t cksum = RAWOBJ_EMPTY; + __u32 rc = GSS_S_FAILURE; + + acceptor_flag = kctx->kc_initiate ? 0 : FLAG_SENDER_IS_ACCEPTOR; + + /* fill krb5 header */ + LASSERT(token->len >= sizeof(*khdr)); + khdr = (struct krb5_header *) token->data; + + khdr->kh_tok_id = cpu_to_be16(KG_TOK_MIC_MSG); + khdr->kh_flags = acceptor_flag; + khdr->kh_filler = 0xff; + khdr->kh_ec = cpu_to_be16(0xffff); + khdr->kh_rrc = cpu_to_be16(0xffff); + spin_lock(&krb5_seq_lock); + khdr->kh_seq = cpu_to_be64(kctx->kc_seq_send++); + spin_unlock(&krb5_seq_lock); + + /* checksum */ + if (krb5_make_checksum(kctx->kc_enctype, &kctx->kc_keyc, + khdr, msgcnt, msgs, &cksum)) + goto out_err; + + LASSERT(cksum.len >= ke->ke_hash_size); + LASSERT(token->len >= sizeof(*khdr) + ke->ke_hash_size); + memcpy(khdr + 1, cksum.data + cksum.len - ke->ke_hash_size, + ke->ke_hash_size); + + token->len = sizeof(*khdr) + ke->ke_hash_size; + rc = GSS_S_COMPLETE; +out_err: + rawobj_free(&cksum); + return rc; +} + +static +__u32 gss_verify_mic_kerberos(struct gss_ctx *gctx, + int msgcnt, + rawobj_t *msgs, + rawobj_t *token) +{ + struct krb5_ctx *kctx = gctx->internal_ctx_id; + struct krb5_enctype *ke = &enctypes[kctx->kc_enctype]; + struct krb5_header *khdr; + unsigned char acceptor_flag; + rawobj_t cksum = RAWOBJ_EMPTY; + __u32 rc = GSS_S_FAILURE; + + acceptor_flag = kctx->kc_initiate ? FLAG_SENDER_IS_ACCEPTOR : 0; + + if (token->len < sizeof(*khdr)) { + CERROR("short signature: %u\n", token->len); + return GSS_S_DEFECTIVE_TOKEN; + } + + khdr = (struct krb5_header *) token->data; + + /* sanity checks */ + if (be16_to_cpu(khdr->kh_tok_id) != KG_TOK_MIC_MSG) { + CERROR("bad token id\n"); + return GSS_S_DEFECTIVE_TOKEN; + } + if ((khdr->kh_flags & FLAG_SENDER_IS_ACCEPTOR) != acceptor_flag) { + CERROR("bad direction flag\n"); + return GSS_S_BAD_SIG; + } + if (khdr->kh_filler != 0xff) { + CERROR("bad filler\n"); + return GSS_S_DEFECTIVE_TOKEN; + } + if (be16_to_cpu(khdr->kh_ec) != 0xffff || + be16_to_cpu(khdr->kh_rrc) != 0xffff) { + CERROR("bad EC or RRC\n"); + return GSS_S_DEFECTIVE_TOKEN; + } + + if (token->len < sizeof(*khdr) + ke->ke_hash_size) { + CERROR("short signature: %u, require %d\n", + token->len, sizeof(*khdr) + ke->ke_hash_size); + goto out; + } + + if (krb5_make_checksum(kctx->kc_enctype, &kctx->kc_keyc, + khdr, msgcnt, msgs, &cksum)) + return GSS_S_FAILURE; + + LASSERT(cksum.len >= ke->ke_hash_size); + if (memcmp(khdr + 1, cksum.data + cksum.len - ke->ke_hash_size, + ke->ke_hash_size)) { + CERROR("checksum mismatch\n"); + goto out; + } + + rc = GSS_S_COMPLETE; +out: + rawobj_free(&cksum); + return rc; +} + +static +int add_padding(rawobj_t *msg, int msg_buflen, int blocksize) +{ + int padding; + + padding = (blocksize - (msg->len & (blocksize - 1))) & + (blocksize - 1); + if (!padding) + return 0; + + if (msg->len + padding > msg_buflen) { + CERROR("bufsize %u too small: datalen %u, padding %u\n", + msg_buflen, msg->len, padding); + return -EINVAL; + } + + memset(msg->data + msg->len, padding, padding); + msg->len += padding; + return 0; +} + +static +int krb5_encrypt_rawobjs(struct crypto_tfm *tfm, + int inobj_cnt, + rawobj_t *inobjs, + rawobj_t *outobj, + int enc) +{ + struct scatterlist src, dst; + __u8 local_iv[16] = {0}, *buf; + __u32 datalen = 0; + int i, rc; + ENTRY; + + buf = outobj->data; + + for (i = 0; i < inobj_cnt; i++) { + LASSERT(buf + inobjs[i].len <= outobj->data + outobj->len); + + buf_to_sg(&src, inobjs[i].data, inobjs[i].len); + buf_to_sg(&dst, buf, outobj->len - datalen); + + if (enc) + rc = crypto_cipher_encrypt_iv(tfm, &dst, &src, + src.length, local_iv); + else + rc = crypto_cipher_decrypt_iv(tfm, &dst, &src, + src.length, local_iv); + + if (rc) { + CERROR("encrypt error %d\n", rc); + RETURN(rc); + } + + datalen += inobjs[i].len; + buf += inobjs[i].len; + } + + outobj->len = datalen; + RETURN(0); +} + +static +__u32 gss_wrap_kerberos(struct gss_ctx *gctx, + rawobj_t *msg, + int msg_buflen, + rawobj_t *token) +{ + struct krb5_ctx *kctx = gctx->internal_ctx_id; + struct krb5_enctype *ke = &enctypes[kctx->kc_enctype]; + struct krb5_header *khdr; + unsigned char acceptor_flag = FLAG_WRAP_CONFIDENTIAL; + int blocksize; + rawobj_t cksum = RAWOBJ_EMPTY; + rawobj_t data_desc[3], cipher; + __u8 conf[GSS_MAX_CIPHER_BLOCK]; + + acceptor_flag = kctx->kc_initiate ? 0 : FLAG_SENDER_IS_ACCEPTOR; + + /* fill krb5 header */ + LASSERT(token->len >= sizeof(*khdr)); + khdr = (struct krb5_header *) token->data; + + khdr->kh_tok_id = cpu_to_be16(KG_TOK_WRAP_MSG); + khdr->kh_flags = acceptor_flag; + khdr->kh_filler = 0xff; + khdr->kh_ec = cpu_to_be16(0); + khdr->kh_rrc = cpu_to_be16(0); + spin_lock(&krb5_seq_lock); + khdr->kh_seq = cpu_to_be64(kctx->kc_seq_send++); + spin_unlock(&krb5_seq_lock); + + /* generate confounder */ + blocksize = crypto_tfm_alg_blocksize(kctx->kc_keye.kb_tfm); + LASSERT(blocksize <= GSS_MAX_CIPHER_BLOCK); + get_random_bytes(conf, blocksize); + + /* padding the message */ + if (add_padding(msg, msg_buflen, blocksize)) + return GSS_S_FAILURE; + + /* encryption: + * ----------------------------------------- + * | confounder | clear msgs | krb5 header | + * ----------------------------------------- + */ + data_desc[0].data = conf; + data_desc[0].len = blocksize; + data_desc[1].data = msg->data; + data_desc[1].len = msg->len; + data_desc[2].data = (__u8 *) khdr; + data_desc[2].len = sizeof(*khdr); + + cipher.data = (__u8 *) (khdr + 1); + cipher.len = token->len - sizeof(*khdr); + LASSERT(blocksize + msg->len + sizeof(*khdr) <= cipher.len); + + if (krb5_encrypt_rawobjs(kctx->kc_keye.kb_tfm, 3, data_desc, + &cipher, 1)) + return GSS_S_FAILURE; + + /* checksum: + * ----------------------------------------- + * | confounder | clear msgs | krb5 header | + * ----------------------------------------- + */ + data_desc[0].data = conf; + data_desc[0].len = blocksize; + data_desc[1].data = msg->data; + data_desc[1].len = msg->len; + data_desc[2].data = (__u8 *) khdr; + data_desc[2].len = sizeof(*khdr); + + if (krb5_make_checksum(kctx->kc_enctype, &kctx->kc_keyi, + khdr, 3, data_desc, &cksum)) + return GSS_S_FAILURE; + + /* fill in checksum */ + LASSERT(cksum.len >= ke->ke_hash_size); + LASSERT(token->len >= sizeof(*khdr) + cipher.len + ke->ke_hash_size); + memcpy((char *)(khdr + 1) + cipher.len, + cksum.data + cksum.len - ke->ke_hash_size, + ke->ke_hash_size); + rawobj_free(&cksum); + + token->len = sizeof(*khdr) + cipher.len + ke->ke_hash_size; + return GSS_S_COMPLETE; +} + +static +__u32 gss_unwrap_kerberos(struct gss_ctx *gctx, + rawobj_t *token, + rawobj_t *msg) +{ + struct krb5_ctx *kctx = gctx->internal_ctx_id; + struct krb5_enctype *ke = &enctypes[kctx->kc_enctype]; + struct krb5_header *khdr; + unsigned char acceptor_flag = FLAG_WRAP_CONFIDENTIAL; + unsigned char *tmpbuf; + int blocksize, bodysize; + rawobj_t cksum = RAWOBJ_EMPTY; + rawobj_t cipher_in, plain_out; + __u32 rc = GSS_S_FAILURE; + + acceptor_flag = kctx->kc_initiate ? FLAG_SENDER_IS_ACCEPTOR : 0; + + if (token->len < sizeof(*khdr)) { + CERROR("short signature: %u\n", token->len); + return GSS_S_DEFECTIVE_TOKEN; + } + + khdr = (struct krb5_header *) token->data; + + /* sanity check header */ + if (be16_to_cpu(khdr->kh_tok_id) != KG_TOK_WRAP_MSG) { + CERROR("bad token id\n"); + return GSS_S_DEFECTIVE_TOKEN; + } + if ((khdr->kh_flags & FLAG_SENDER_IS_ACCEPTOR) != acceptor_flag) { + CERROR("bad direction flag\n"); + return GSS_S_BAD_SIG; + } + if (khdr->kh_filler != 0xff) { + CERROR("bad filler\n"); + return GSS_S_DEFECTIVE_TOKEN; + } + if (be16_to_cpu(khdr->kh_ec) != 0x0 || + be16_to_cpu(khdr->kh_rrc) != 0x0) { + CERROR("bad EC or RRC\n"); + return GSS_S_DEFECTIVE_TOKEN; + } + + blocksize = crypto_tfm_alg_blocksize(kctx->kc_keye.kb_tfm); + + /* token: + * ---------------------------------------- + * | krb5 header | cipher text | checksum | + * ---------------------------------------- + */ + bodysize = token->len - sizeof(*khdr) - ke->ke_hash_size; + + if (bodysize % blocksize) { + CERROR("odd bodysize %d\n", bodysize); + return GSS_S_DEFECTIVE_TOKEN; + } + + if (bodysize <= blocksize + sizeof(*khdr)) { + CERROR("incomplete token: bodysize %d\n", bodysize); + return GSS_S_DEFECTIVE_TOKEN; + } + + if (msg->len < bodysize - blocksize - sizeof(*khdr)) { + CERROR("buffer too small: %u, require %d\n", + msg->len, bodysize - blocksize); + return GSS_S_FAILURE; + } + + /* decrypting */ + OBD_ALLOC(tmpbuf, bodysize); + if (!tmpbuf) + return GSS_S_FAILURE; + + cipher_in.data = (__u8 *) (khdr + 1); + cipher_in.len = bodysize; + plain_out.data = tmpbuf; + plain_out.len = bodysize; + + if (krb5_encrypt_rawobjs(kctx->kc_keye.kb_tfm, 1, + &cipher_in, &plain_out, 0)) { + CERROR("error decrypt\n"); + goto out_free; + } + LASSERT(plain_out.len == bodysize); + + /* clear text: + * ----------------------------------------- + * | confounder | clear msgs | krb5 header | + * ----------------------------------------- + */ + + /* last part must be identical to the krb5 header */ + if (memcmp(khdr, plain_out.data + plain_out.len - sizeof(*khdr), + sizeof(*khdr))) { + CERROR("decrypted header mismatch\n"); + goto out_free; + } + + /* verify checksum */ + if (krb5_make_checksum(kctx->kc_enctype, &kctx->kc_keyi, + khdr, 1, &plain_out, &cksum)) + goto out_free; + + LASSERT(cksum.len >= ke->ke_hash_size); + if (memcmp((char *)(khdr + 1) + bodysize, + cksum.data + cksum.len - ke->ke_hash_size, + ke->ke_hash_size)) { + CERROR("cksum mismatch\n"); + goto out_free; + } + + msg->len = bodysize - sizeof(*khdr) - blocksize; + memcpy(msg->data, tmpbuf + blocksize, msg->len); + + rc = GSS_S_COMPLETE; +out_free: + OBD_FREE(tmpbuf, bodysize); + rawobj_free(&cksum); + return rc; +} + +static +__u32 gss_plain_encrypt_kerberos(struct gss_ctx *ctx, + int length, + void *in_buf, + void *out_buf) +{ + struct krb5_ctx *kctx = ctx->internal_ctx_id; + __u32 rc; + + rc = krb5_encrypt(kctx->kc_keye.kb_tfm, 0, + NULL, in_buf, out_buf, length); + if (rc) + CERROR("plain encrypt error: %d\n", rc); + + return rc; +} + +static struct gss_api_ops gss_kerberos_ops = { + .gss_import_sec_context = gss_import_sec_context_kerberos, + .gss_copy_reverse_context = gss_copy_reverse_context_kerberos, + .gss_inquire_context = gss_inquire_context_kerberos, + .gss_get_mic = gss_get_mic_kerberos, + .gss_verify_mic = gss_verify_mic_kerberos, + .gss_wrap = gss_wrap_kerberos, + .gss_unwrap = gss_unwrap_kerberos, + .gss_plain_encrypt = gss_plain_encrypt_kerberos, + .gss_delete_sec_context = gss_delete_sec_context_kerberos, +}; + +static struct subflavor_desc gss_kerberos_sfs[] = { + { + .sf_subflavor = SPTLRPC_SUBFLVR_KRB5, + .sf_qop = 0, + .sf_service = SPTLRPC_SVC_NONE, + .sf_name = "krb5" + }, + { + .sf_subflavor = SPTLRPC_SUBFLVR_KRB5I, + .sf_qop = 0, + .sf_service = SPTLRPC_SVC_AUTH, + .sf_name = "krb5i" + }, + { + .sf_subflavor = SPTLRPC_SUBFLVR_KRB5P, + .sf_qop = 0, + .sf_service = SPTLRPC_SVC_PRIV, + .sf_name = "krb5p" + }, +}; + +/* + * currently we leave module owner NULL + */ +static struct gss_api_mech gss_kerberos_mech = { + .gm_owner = NULL, /*THIS_MODULE, */ + .gm_name = "krb5", + .gm_oid = (rawobj_t) + {9, "\052\206\110\206\367\022\001\002\002"}, + .gm_ops = &gss_kerberos_ops, + .gm_sf_num = 3, + .gm_sfs = gss_kerberos_sfs, +}; + +int __init init_kerberos_module(void) +{ + int status; + + status = lgss_mech_register(&gss_kerberos_mech); + if (status) + CERROR("Failed to register kerberos gss mechanism!\n"); + return status; +} + +void __exit cleanup_kerberos_module(void) +{ + lgss_mech_unregister(&gss_kerberos_mech); +} diff --git a/lustre/ptlrpc/gss/gss_mech_switch.c b/lustre/ptlrpc/gss/gss_mech_switch.c new file mode 100644 index 0000000..a6493d2 --- /dev/null +++ b/lustre/ptlrpc/gss/gss_mech_switch.c @@ -0,0 +1,332 @@ +/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*- + * vim:expandtab:shiftwidth=8:tabstop=8: + * + * Modifications for Lustre + * Copyright 2004 - 2006, Cluster File Systems, Inc. + * All rights reserved + * Author: Eric Mei + */ + +/* + * linux/net/sunrpc/gss_mech_switch.c + * + * Copyright (c) 2001 The Regents of the University of Michigan. + * All rights reserved. + * + * J. Bruce Fields + * + * 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 EXPORT_SYMTAB +# define EXPORT_SYMTAB +#endif +#define DEBUG_SUBSYSTEM S_SEC +#ifdef __KERNEL__ +#include +#include +#include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "gss_err.h" +#include "gss_internal.h" +#include "gss_api.h" + +static LIST_HEAD(registered_mechs); +static spinlock_t registered_mechs_lock = SPIN_LOCK_UNLOCKED; + +int lgss_mech_register(struct gss_api_mech *gm) +{ + spin_lock(®istered_mechs_lock); + list_add(&gm->gm_list, ®istered_mechs); + spin_unlock(®istered_mechs_lock); + CWARN("register %s mechanism\n", gm->gm_name); + return 0; +} + +void lgss_mech_unregister(struct gss_api_mech *gm) +{ + spin_lock(®istered_mechs_lock); + list_del(&gm->gm_list); + spin_unlock(®istered_mechs_lock); + CWARN("unregister %s mechanism\n", gm->gm_name); +} + + +struct gss_api_mech *lgss_mech_get(struct gss_api_mech *gm) +{ + __module_get(gm->gm_owner); + return gm; +} + +struct gss_api_mech *lgss_name_to_mech(char *name) +{ + struct gss_api_mech *pos, *gm = NULL; + + spin_lock(®istered_mechs_lock); + list_for_each_entry(pos, ®istered_mechs, gm_list) { + if (0 == strcmp(name, pos->gm_name)) { + if (!try_module_get(pos->gm_owner)) + continue; + gm = pos; + break; + } + } + spin_unlock(®istered_mechs_lock); + return gm; + +} + +static inline +int mech_supports_subflavor(struct gss_api_mech *gm, __u32 subflavor) +{ + int i; + + for (i = 0; i < gm->gm_sf_num; i++) { + if (gm->gm_sfs[i].sf_subflavor == subflavor) + return 1; + } + return 0; +} + +struct gss_api_mech *lgss_subflavor_to_mech(__u32 subflavor) +{ + struct gss_api_mech *pos, *gm = NULL; + + spin_lock(®istered_mechs_lock); + list_for_each_entry(pos, ®istered_mechs, gm_list) { + if (!try_module_get(pos->gm_owner)) + continue; + if (!mech_supports_subflavor(pos, subflavor)) { + module_put(pos->gm_owner); + continue; + } + gm = pos; + break; + } + spin_unlock(®istered_mechs_lock); + return gm; +} + +void lgss_mech_put(struct gss_api_mech *gm) +{ + module_put(gm->gm_owner); +} + +/* The mech could probably be determined from the token instead, but it's just + * as easy for now to pass it in. */ +__u32 lgss_import_sec_context(rawobj_t *input_token, + struct gss_api_mech *mech, + struct gss_ctx **ctx_id) +{ + OBD_ALLOC(*ctx_id, sizeof(**ctx_id)); + if (*ctx_id == NULL) + return GSS_S_FAILURE; + + (*ctx_id)->mech_type = lgss_mech_get(mech); + + LASSERT(mech); + LASSERT(mech->gm_ops); + LASSERT(mech->gm_ops->gss_import_sec_context); + return mech->gm_ops->gss_import_sec_context(input_token, *ctx_id); +} + +__u32 lgss_copy_reverse_context(struct gss_ctx *ctx_id, + struct gss_ctx **ctx_id_new) +{ + struct gss_api_mech *mech = ctx_id->mech_type; + __u32 major; + + LASSERT(mech); + + OBD_ALLOC(*ctx_id_new, sizeof(**ctx_id_new)); + if (*ctx_id_new == NULL) + return GSS_S_FAILURE; + + (*ctx_id_new)->mech_type = lgss_mech_get(mech); + + LASSERT(mech); + LASSERT(mech->gm_ops); + LASSERT(mech->gm_ops->gss_copy_reverse_context); + + major = mech->gm_ops->gss_copy_reverse_context(ctx_id, *ctx_id_new); + if (major != GSS_S_COMPLETE) { + lgss_mech_put(mech); + OBD_FREE(*ctx_id_new, sizeof(**ctx_id_new)); + *ctx_id_new = NULL; + } + return major; +} + +/* + * this interface is much simplified, currently we only need endtime. + */ +__u32 lgss_inquire_context(struct gss_ctx *context_handle, + unsigned long *endtime) +{ + LASSERT(context_handle); + LASSERT(context_handle->mech_type); + LASSERT(context_handle->mech_type->gm_ops); + LASSERT(context_handle->mech_type->gm_ops->gss_inquire_context); + + return context_handle->mech_type->gm_ops + ->gss_inquire_context(context_handle, + endtime); +} + +/* gss_get_mic: compute a mic over message and return mic_token. */ +__u32 lgss_get_mic(struct gss_ctx *context_handle, + int msgcnt, + rawobj_t *msg, + rawobj_t *mic_token) +{ + LASSERT(context_handle); + LASSERT(context_handle->mech_type); + LASSERT(context_handle->mech_type->gm_ops); + LASSERT(context_handle->mech_type->gm_ops->gss_get_mic); + + return context_handle->mech_type->gm_ops + ->gss_get_mic(context_handle, + msgcnt, + msg, + mic_token); +} + +/* gss_verify_mic: check whether the provided mic_token verifies message. */ +__u32 lgss_verify_mic(struct gss_ctx *context_handle, + int msgcnt, + rawobj_t *msg, + rawobj_t *mic_token) +{ + LASSERT(context_handle); + LASSERT(context_handle->mech_type); + LASSERT(context_handle->mech_type->gm_ops); + LASSERT(context_handle->mech_type->gm_ops->gss_verify_mic); + + return context_handle->mech_type->gm_ops + ->gss_verify_mic(context_handle, + msgcnt, + msg, + mic_token); +} + +#if 0 +__u32 lgss_wrap(struct gss_ctx *context_handle, + __u32 qop, + rawobj_buf_t *inbuf, + rawobj_t *outbuf) +{ + LASSERT(context_handle); + LASSERT(context_handle->mech_type); + LASSERT(context_handle->mech_type->gm_ops); + LASSERT(context_handle->mech_type->gm_ops->gss_wrap); + + return context_handle->mech_type->gm_ops + ->gss_wrap(context_handle, qop, inbuf, outbuf); +} +#endif + +__u32 lgss_wrap(struct gss_ctx *context_handle, + rawobj_t *msg, + int msg_buflen, + rawobj_t *out_token) +{ + LASSERT(context_handle); + LASSERT(context_handle->mech_type); + LASSERT(context_handle->mech_type->gm_ops); + LASSERT(context_handle->mech_type->gm_ops->gss_wrap); + + return context_handle->mech_type->gm_ops + ->gss_wrap(context_handle, msg, msg_buflen, out_token); +} + +__u32 lgss_unwrap(struct gss_ctx *context_handle, + rawobj_t *token, + rawobj_t *out_msg) +{ + LASSERT(context_handle); + LASSERT(context_handle->mech_type); + LASSERT(context_handle->mech_type->gm_ops); + LASSERT(context_handle->mech_type->gm_ops->gss_unwrap); + + return context_handle->mech_type->gm_ops + ->gss_unwrap(context_handle, token, out_msg); +} + + +__u32 lgss_plain_encrypt(struct gss_ctx *ctx, + int length, + void *in_buf, + void *out_buf) +{ + LASSERT(ctx); + LASSERT(ctx->mech_type); + LASSERT(ctx->mech_type->gm_ops); + LASSERT(ctx->mech_type->gm_ops->gss_plain_encrypt); + + return ctx->mech_type->gm_ops + ->gss_plain_encrypt(ctx, length, in_buf, out_buf); +} + +/* gss_delete_sec_context: free all resources associated with context_handle. + * Note this differs from the RFC 2744-specified prototype in that we don't + * bother returning an output token, since it would never be used anyway. */ + +__u32 lgss_delete_sec_context(struct gss_ctx **context_handle) +{ + struct gss_api_mech *mech; + + CDEBUG(D_SEC, "deleting %p\n", *context_handle); + + if (!*context_handle) + return(GSS_S_NO_CONTEXT); + + mech = (*context_handle)->mech_type; + if ((*context_handle)->internal_ctx_id != 0) { + LASSERT(mech); + LASSERT(mech->gm_ops); + LASSERT(mech->gm_ops->gss_delete_sec_context); + mech->gm_ops->gss_delete_sec_context( + (*context_handle)->internal_ctx_id); + } + if (mech) + lgss_mech_put(mech); + + OBD_FREE(*context_handle, sizeof(**context_handle)); + *context_handle=NULL; + return GSS_S_COMPLETE; +} diff --git a/lustre/ptlrpc/gss/gss_rawobj.c b/lustre/ptlrpc/gss/gss_rawobj.c new file mode 100644 index 0000000..847cb4d --- /dev/null +++ b/lustre/ptlrpc/gss/gss_rawobj.c @@ -0,0 +1,195 @@ +/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*- + * vim:expandtab:shiftwidth=8:tabstop=8: + * + * Copyright (C) 2004 Cluster File Systems, Inc. + * + * This file is part of Lustre, http://www.lustre.org. + * + * Lustre is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * Lustre is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Lustre; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef EXPORT_SYMTAB +# define EXPORT_SYMTAB +#endif +#define DEBUG_SUBSYSTEM S_SEC + +#include +#include +#include +#include + +#include "gss_internal.h" + +int rawobj_alloc(rawobj_t *obj, char *buf, int len) +{ + LASSERT(obj); + LASSERT(len >= 0); + + obj->len = len; + if (len) { + OBD_ALLOC(obj->data, len); + if (!obj->data) { + obj->len = 0; + RETURN(-ENOMEM); + } + memcpy(obj->data, buf, len); + } else + obj->data = NULL; + return 0; +} + +void rawobj_free(rawobj_t *obj) +{ + LASSERT(obj); + + if (obj->len) { + LASSERT(obj->data); + OBD_FREE(obj->data, obj->len); + obj->len = 0; + obj->data = NULL; + } else + LASSERT(!obj->data); +} + +int rawobj_equal(rawobj_t *a, rawobj_t *b) +{ + LASSERT(a && b); + + return (a->len == b->len && + (!a->len || !memcmp(a->data, b->data, a->len))); +} + +int rawobj_dup(rawobj_t *dest, rawobj_t *src) +{ + LASSERT(src && dest); + + dest->len = src->len; + if (dest->len) { + OBD_ALLOC(dest->data, dest->len); + if (!dest->data) { + dest->len = 0; + return -ENOMEM; + } + memcpy(dest->data, src->data, dest->len); + } else + dest->data = NULL; + return 0; +} + +int rawobj_serialize(rawobj_t *obj, __u32 **buf, __u32 *buflen) +{ + __u32 len; + + LASSERT(obj); + LASSERT(buf); + LASSERT(buflen); + + len = size_round4(obj->len); + + if (*buflen < 4 + len) { + CERROR("buflen %u < %u\n", *buflen, 4 + len); + return -EINVAL; + } + + *(*buf)++ = cpu_to_le32(obj->len); + memcpy(*buf, obj->data, obj->len); + *buf += (len >> 2); + *buflen -= (4 + len); + + return 0; +} + +static int __rawobj_extract(rawobj_t *obj, __u32 **buf, __u32 *buflen, + int alloc, int local) +{ + __u32 len; + + if (*buflen < sizeof(__u32)) { + CERROR("buflen %u\n", *buflen); + return -EINVAL; + } + + obj->len = *(*buf)++; + if (!local) + obj->len = le32_to_cpu(obj->len); + *buflen -= sizeof(__u32); + + if (!obj->len) { + obj->data = NULL; + return 0; + } + + len = local ? obj->len : size_round4(obj->len); + if (*buflen < len) { + CERROR("buflen %u < %u\n", *buflen, len); + obj->len = 0; + return -EINVAL; + } + + if (!alloc) + obj->data = (__u8 *) *buf; + else { + OBD_ALLOC(obj->data, obj->len); + if (!obj->data) { + CERROR("fail to alloc %u bytes\n", obj->len); + obj->len = 0; + return -ENOMEM; + } + memcpy(obj->data, *buf, obj->len); + } + + *((char **)buf) += len; + *buflen -= len; + + return 0; +} + +int rawobj_extract(rawobj_t *obj, __u32 **buf, __u32 *buflen) +{ + return __rawobj_extract(obj, buf, buflen, 0, 0); +} + +int rawobj_extract_alloc(rawobj_t *obj, __u32 **buf, __u32 *buflen) +{ + return __rawobj_extract(obj, buf, buflen, 1, 0); +} + +int rawobj_extract_local(rawobj_t *obj, __u32 **buf, __u32 *buflen) +{ + return __rawobj_extract(obj, buf, buflen, 0, 1); +} + +int rawobj_from_netobj(rawobj_t *rawobj, netobj_t *netobj) +{ + rawobj->len = netobj->len; + rawobj->data = netobj->data; + return 0; +} + +int rawobj_from_netobj_alloc(rawobj_t *rawobj, netobj_t *netobj) +{ + rawobj->len = 0; + rawobj->data = NULL; + + if (netobj->len == 0) + return 0; + + OBD_ALLOC(rawobj->data, netobj->len); + if (rawobj->data == NULL) + return -ENOMEM; + + rawobj->len = netobj->len; + memcpy(rawobj->data, netobj->data, netobj->len); + return 0; +} diff --git a/lustre/ptlrpc/gss/gss_svc_upcall.c b/lustre/ptlrpc/gss/gss_svc_upcall.c new file mode 100644 index 0000000..776ae06 --- /dev/null +++ b/lustre/ptlrpc/gss/gss_svc_upcall.c @@ -0,0 +1,976 @@ +/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*- + * vim:expandtab:shiftwidth=8:tabstop=8: + * + * Modifications for Lustre + * Copyright 2004 - 2006, Cluster File Systems, Inc. + * All rights reserved + * Author: Eric Mei + */ + +/* + * Neil Brown + * J. Bruce Fields + * Andy Adamson + * Dug Song + * + * RPCSEC_GSS server authentication. + * This implements RPCSEC_GSS as defined in rfc2203 (rpcsec_gss) and rfc2078 + * (gssapi) + * + * The RPCSEC_GSS involves three stages: + * 1/ context creation + * 2/ data exchange + * 3/ context destruction + * + * Context creation is handled largely by upcalls to user-space. + * In particular, GSS_Accept_sec_context is handled by an upcall + * Data exchange is handled entirely within the kernel + * In particular, GSS_GetMIC, GSS_VerifyMIC, GSS_Seal, GSS_Unseal are in-kernel. + * Context destruction is handled in-kernel + * GSS_Delete_sec_context is in-kernel + * + * Context creation is initiated by a RPCSEC_GSS_INIT request arriving. + * The context handle and gss_token are used as a key into the rpcsec_init cache. + * The content of this cache includes some of the outputs of GSS_Accept_sec_context, + * being major_status, minor_status, context_handle, reply_token. + * These are sent back to the client. + * Sequence window management is handled by the kernel. The window size if currently + * a compile time constant. + * + * When user-space is happy that a context is established, it places an entry + * in the rpcsec_context cache. The key for this cache is the context_handle. + * The content includes: + * uid/gidlist - for determining access rights + * mechanism type + * mechanism specific information, such as a key + * + */ + +#define DEBUG_SUBSYSTEM S_SEC +#ifdef __KERNEL__ +#include +#include +#include +#include +#include +#else +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "gss_err.h" +#include "gss_internal.h" +#include "gss_api.h" + +#define GSS_SVC_UPCALL_TIMEOUT (20) + +static spinlock_t __ctx_index_lock = SPIN_LOCK_UNLOCKED; +static __u64 __ctx_index = 1ULL; + +__u64 gss_get_next_ctx_index(void) +{ + __u64 idx; + + spin_lock(&__ctx_index_lock); + idx = __ctx_index++; + spin_unlock(&__ctx_index_lock); + + return idx; +} + +static inline +unsigned long hash_mem(char *buf, int length, int bits) +{ + unsigned long hash = 0; + unsigned long l = 0; + int len = 0; + unsigned char c; + + do { + if (len == length) { + c = (char) len; + len = -1; + } else + c = *buf++; + + l = (l << 8) | c; + len++; + + if ((len & (BITS_PER_LONG/8-1)) == 0) + hash = hash_long(hash^l, BITS_PER_LONG); + } while (len); + + return hash >> (BITS_PER_LONG - bits); +} + +/**************************************** + * rsi cache * + ****************************************/ + +#define RSI_HASHBITS (6) +#define RSI_HASHMAX (1 << RSI_HASHBITS) +#define RSI_HASHMASK (RSI_HASHMAX - 1) + +struct rsi { + struct cache_head h; + __u32 lustre_svc; + __u64 nid; + wait_queue_head_t waitq; + rawobj_t in_handle, in_token; + rawobj_t out_handle, out_token; + int major_status, minor_status; +}; + +static struct cache_head *rsi_table[RSI_HASHMAX]; +static struct cache_detail rsi_cache; +static struct rsi *rsi_lookup(struct rsi *item, int set); + +static +void rsi_free(struct rsi *rsi) +{ + rawobj_free(&rsi->in_handle); + rawobj_free(&rsi->in_token); + rawobj_free(&rsi->out_handle); + rawobj_free(&rsi->out_token); +} + +static +void rsi_put(struct cache_head *item, struct cache_detail *cd) +{ + struct rsi *rsi = container_of(item, struct rsi, h); + + LASSERT(atomic_read(&item->refcnt) > 0); + + if (cache_put(item, cd)) { + LASSERT(item->next == NULL); + rsi_free(rsi); + kfree(rsi); /* created by cache mgmt using kmalloc */ + } +} + +static inline +int rsi_hash(struct rsi *item) +{ + return hash_mem((char *)item->in_handle.data, item->in_handle.len, + RSI_HASHBITS) ^ + hash_mem((char *)item->in_token.data, item->in_token.len, + RSI_HASHBITS); +} + +static inline +int rsi_match(struct rsi *item, struct rsi *tmp) +{ + return (rawobj_equal(&item->in_handle, &tmp->in_handle) && + rawobj_equal(&item->in_token, &tmp->in_token)); +} + +static +void rsi_request(struct cache_detail *cd, + struct cache_head *h, + char **bpp, int *blen) +{ + struct rsi *rsi = container_of(h, struct rsi, h); + __u64 index = 0; + + /* if in_handle is null, provide kernel suggestion */ + if (rsi->in_handle.len == 0) + index = gss_get_next_ctx_index(); + + qword_addhex(bpp, blen, (char *) &rsi->lustre_svc, + sizeof(rsi->lustre_svc)); + qword_addhex(bpp, blen, (char *) &rsi->nid, sizeof(rsi->nid)); + qword_addhex(bpp, blen, (char *) &index, sizeof(index)); + qword_addhex(bpp, blen, rsi->in_handle.data, rsi->in_handle.len); + qword_addhex(bpp, blen, rsi->in_token.data, rsi->in_token.len); + (*bpp)[-1] = '\n'; +} + +static inline +void rsi_init(struct rsi *new, struct rsi *item) +{ + new->out_handle = RAWOBJ_EMPTY; + new->out_token = RAWOBJ_EMPTY; + + new->in_handle = item->in_handle; + item->in_handle = RAWOBJ_EMPTY; + new->in_token = item->in_token; + item->in_token = RAWOBJ_EMPTY; + + new->lustre_svc = item->lustre_svc; + new->nid = item->nid; + init_waitqueue_head(&new->waitq); +} + +static inline +void rsi_update(struct rsi *new, struct rsi *item) +{ + LASSERT(new->out_handle.len == 0); + LASSERT(new->out_token.len == 0); + + new->out_handle = item->out_handle; + item->out_handle = RAWOBJ_EMPTY; + new->out_token = item->out_token; + item->out_token = RAWOBJ_EMPTY; + + new->major_status = item->major_status; + new->minor_status = item->minor_status; +} + +static +int rsi_parse(struct cache_detail *cd, char *mesg, int mlen) +{ + char *buf = mesg; + char *ep; + int len; + struct rsi rsii, *rsip = NULL; + time_t expiry; + int status = -EINVAL; + ENTRY; + + + memset(&rsii, 0, sizeof(rsii)); + + /* handle */ + len = qword_get(&mesg, buf, mlen); + if (len < 0) + goto out; + if (rawobj_alloc(&rsii.in_handle, buf, len)) { + status = -ENOMEM; + goto out; + } + + /* token */ + len = qword_get(&mesg, buf, mlen); + if (len < 0) + goto out; + if (rawobj_alloc(&rsii.in_token, buf, len)) { + status = -ENOMEM; + goto out; + } + + /* expiry */ + expiry = get_expiry(&mesg); + if (expiry == 0) + goto out; + + len = qword_get(&mesg, buf, mlen); + if (len <= 0) + goto out; + + /* major */ + rsii.major_status = simple_strtol(buf, &ep, 10); + if (*ep) + goto out; + + /* minor */ + len = qword_get(&mesg, buf, mlen); + if (len <= 0) + goto out; + rsii.minor_status = simple_strtol(buf, &ep, 10); + if (*ep) + goto out; + + /* out_handle */ + len = qword_get(&mesg, buf, mlen); + if (len < 0) + goto out; + if (rawobj_alloc(&rsii.out_handle, buf, len)) { + status = -ENOMEM; + goto out; + } + + /* out_token */ + len = qword_get(&mesg, buf, mlen); + if (len < 0) + goto out; + if (rawobj_alloc(&rsii.out_token, buf, len)) { + status = -ENOMEM; + goto out; + } + + rsii.h.expiry_time = expiry; + rsip = rsi_lookup(&rsii, 1); + status = 0; +out: + rsi_free(&rsii); + if (rsip) { + wake_up_all(&rsip->waitq); + rsi_put(&rsip->h, &rsi_cache); + } + + if (status) + CERROR("rsi parse error %d\n", status); + RETURN(status); +} + +static struct cache_detail rsi_cache = { + .hash_size = RSI_HASHMAX, + .hash_table = rsi_table, + .name = "auth.ptlrpcs.init", + .cache_put = rsi_put, + .cache_request = rsi_request, + .cache_parse = rsi_parse, +}; + +static DefineSimpleCacheLookup(rsi, 0) + +/**************************************** + * rsc cache * + ****************************************/ + +#define RSC_HASHBITS (10) +#define RSC_HASHMAX (1 << RSC_HASHBITS) +#define RSC_HASHMASK (RSC_HASHMAX - 1) + +struct rsc { + struct cache_head h; + struct obd_device *target; + rawobj_t handle; + struct gss_svc_ctx ctx; +}; + +static struct cache_head *rsc_table[RSC_HASHMAX]; +static struct cache_detail rsc_cache; +static struct rsc *rsc_lookup(struct rsc *item, int set); + +static +void rsc_free(struct rsc *rsci) +{ + rawobj_free(&rsci->handle); + rawobj_free(&rsci->ctx.gsc_rvs_hdl); + lgss_delete_sec_context(&rsci->ctx.gsc_mechctx); +} + +static +void rsc_put(struct cache_head *item, struct cache_detail *cd) +{ + struct rsc *rsci = container_of(item, struct rsc, h); + + LASSERT(atomic_read(&item->refcnt) > 0); + + if (cache_put(item, cd)) { + LASSERT(item->next == NULL); + rsc_free(rsci); + kfree(rsci); /* created by cache mgmt using kmalloc */ + } +} + +static inline +int rsc_hash(struct rsc *rsci) +{ + return hash_mem((char *)rsci->handle.data, + rsci->handle.len, RSC_HASHBITS); +} + +static inline +int rsc_match(struct rsc *new, struct rsc *tmp) +{ + return rawobj_equal(&new->handle, &tmp->handle); +} + +static inline +void rsc_init(struct rsc *new, struct rsc *tmp) +{ + new->handle = tmp->handle; + tmp->handle = RAWOBJ_EMPTY; + + new->target = NULL; + memset(&new->ctx, 0, sizeof(new->ctx)); + new->ctx.gsc_rvs_hdl = RAWOBJ_EMPTY; +} + +static inline +void rsc_update(struct rsc *new, struct rsc *tmp) +{ + new->ctx = tmp->ctx; + tmp->ctx.gsc_rvs_hdl = RAWOBJ_EMPTY; + tmp->ctx.gsc_mechctx = NULL; + + memset(&new->ctx.gsc_seqdata, 0, sizeof(new->ctx.gsc_seqdata)); + spin_lock_init(&new->ctx.gsc_seqdata.ssd_lock); +} + +static +int rsc_parse(struct cache_detail *cd, char *mesg, int mlen) +{ + char *buf = mesg; + int len, rv, tmp_int; + struct rsc rsci, *rscp = NULL; + time_t expiry; + int status = -EINVAL; + + memset(&rsci, 0, sizeof(rsci)); + + /* context handle */ + len = qword_get(&mesg, buf, mlen); + if (len < 0) goto out; + status = -ENOMEM; + if (rawobj_alloc(&rsci.handle, buf, len)) + goto out; + + rsci.h.flags = 0; + /* expiry */ + expiry = get_expiry(&mesg); + status = -EINVAL; + if (expiry == 0) + goto out; + + /* remote flag */ + rv = get_int(&mesg, &tmp_int); + if (rv) { + CERROR("fail to get remote flag\n"); + goto out; + } + rsci.ctx.gsc_remote = (tmp_int != 0); + + /* root user flag */ + rv = get_int(&mesg, &tmp_int); + if (rv) { + CERROR("fail to get oss user flag\n"); + goto out; + } + rsci.ctx.gsc_usr_root = (tmp_int != 0); + + /* mds user flag */ + rv = get_int(&mesg, &tmp_int); + if (rv) { + CERROR("fail to get mds user flag\n"); + goto out; + } + rsci.ctx.gsc_usr_mds = (tmp_int != 0); + + /* mapped uid */ + rv = get_int(&mesg, (int *) &rsci.ctx.gsc_mapped_uid); + if (rv) { + CERROR("fail to get mapped uid\n"); + goto out; + } + + /* uid, or NEGATIVE */ + rv = get_int(&mesg, (int *) &rsci.ctx.gsc_uid); + if (rv == -EINVAL) + goto out; + if (rv == -ENOENT) { + CERROR("NOENT? set rsc entry negative\n"); + set_bit(CACHE_NEGATIVE, &rsci.h.flags); + } else { + struct gss_api_mech *gm; + rawobj_t tmp_buf; + unsigned long ctx_expiry; + + /* gid */ + if (get_int(&mesg, (int *) &rsci.ctx.gsc_gid)) + goto out; + + /* mech name */ + len = qword_get(&mesg, buf, mlen); + if (len < 0) + goto out; + gm = lgss_name_to_mech(buf); + status = -EOPNOTSUPP; + if (!gm) + goto out; + + status = -EINVAL; + /* mech-specific data: */ + len = qword_get(&mesg, buf, mlen); + if (len < 0) { + lgss_mech_put(gm); + goto out; + } + tmp_buf.len = len; + tmp_buf.data = (unsigned char *)buf; + if (lgss_import_sec_context(&tmp_buf, gm, + &rsci.ctx.gsc_mechctx)) { + lgss_mech_put(gm); + goto out; + } + + /* currently the expiry time passed down from user-space + * is invalid, here we retrive it from mech. + */ + if (lgss_inquire_context(rsci.ctx.gsc_mechctx, &ctx_expiry)) { + CERROR("unable to get expire time, drop it\n"); + lgss_mech_put(gm); + goto out; + } + expiry = (time_t) ctx_expiry; + + lgss_mech_put(gm); + } + + rsci.h.expiry_time = expiry; + rscp = rsc_lookup(&rsci, 1); + status = 0; +out: + rsc_free(&rsci); + if (rscp) + rsc_put(&rscp->h, &rsc_cache); + + if (status) + CERROR("parse rsc error %d\n", status); + return status; +} + +/**************************************** + * rsc cache flush * + ****************************************/ + +typedef int rsc_entry_match(struct rsc *rscp, long data); + +static +void rsc_flush(rsc_entry_match *match, long data) +{ + struct cache_head **ch; + struct rsc *rscp; + int n; + ENTRY; + + write_lock(&rsc_cache.hash_lock); + for (n = 0; n < RSC_HASHMAX; n++) { + for (ch = &rsc_cache.hash_table[n]; *ch;) { + rscp = container_of(*ch, struct rsc, h); + + if (!match(rscp, data)) { + ch = &((*ch)->next); + continue; + } + + /* it seems simply set NEGATIVE doesn't work */ + *ch = (*ch)->next; + rscp->h.next = NULL; + cache_get(&rscp->h); + set_bit(CACHE_NEGATIVE, &rscp->h.flags); + rsc_put(&rscp->h, &rsc_cache); + rsc_cache.entries--; + } + } + write_unlock(&rsc_cache.hash_lock); + EXIT; +} + +static +int match_uid(struct rsc *rscp, long uid) +{ + if ((int) uid == -1) + return 1; + return ((int) rscp->ctx.gsc_uid == (int) uid); +} + +static +int match_target(struct rsc *rscp, long target) +{ + return (rscp->target == (struct obd_device *) target); +} + +static inline +void rsc_flush_uid(int uid) +{ + if (uid == -1) + CWARN("flush all gss contexts...\n"); + + rsc_flush(match_uid, (long) uid); +} + +static inline +void rsc_flush_target(struct obd_device *target) +{ + rsc_flush(match_target, (long) target); +} + +void gss_secsvc_flush(struct obd_device *target) +{ + rsc_flush_target(target); +} +EXPORT_SYMBOL(gss_secsvc_flush); + +static struct cache_detail rsc_cache = { + .hash_size = RSC_HASHMAX, + .hash_table = rsc_table, + .name = "auth.ptlrpcs.context", + .cache_put = rsc_put, + .cache_parse = rsc_parse, +}; + +static DefineSimpleCacheLookup(rsc, 0); + +static +struct rsc *gss_svc_searchbyctx(rawobj_t *handle) +{ + struct rsc rsci; + struct rsc *found; + + memset(&rsci, 0, sizeof(rsci)); + if (rawobj_dup(&rsci.handle, handle)) + return NULL; + + found = rsc_lookup(&rsci, 0); + rsc_free(&rsci); + if (!found) + return NULL; + if (cache_check(&rsc_cache, &found->h, NULL)) + return NULL; + return found; +} + +int gss_svc_upcall_install_rvs_ctx(struct obd_import *imp, + struct gss_sec *gsec, + struct gss_cli_ctx *gctx) +{ + struct rsc rsci, *rscp; + unsigned long ctx_expiry; + __u32 major; + ENTRY; + + memset(&rsci, 0, sizeof(rsci)); + + if (rawobj_alloc(&rsci.handle, (char *) &gsec->gs_rvs_hdl, + sizeof(gsec->gs_rvs_hdl))) { + CERROR("unable alloc handle\n"); + RETURN(-ENOMEM); + } + + major = lgss_copy_reverse_context(gctx->gc_mechctx, + &rsci.ctx.gsc_mechctx); + if (major != GSS_S_COMPLETE) { + CERROR("unable to copy reverse context\n"); + rsc_free(&rsci); + RETURN(-ENOMEM); + } + + if (lgss_inquire_context(rsci.ctx.gsc_mechctx, &ctx_expiry)) { + CERROR("unable to get expire time, drop it\n"); + rsc_free(&rsci); + RETURN(-EINVAL); + } + + rsci.h.expiry_time = (time_t) ctx_expiry; + rsci.target = imp->imp_obd; + + rscp = rsc_lookup(&rsci, 1); + rsc_free(&rsci); + if (rscp) + rsc_put(&rscp->h, &rsc_cache); + + CWARN("client installed reverse svc ctx to %s: idx %llx\n", + imp->imp_obd->u.cli.cl_target_uuid.uuid, + gsec->gs_rvs_hdl); + + imp->imp_next_reconnect = gss_round_imp_reconnect(ctx_expiry); + CWARN("import to %s: set force reconnect at %lu(%lds valid time)\n", + imp->imp_obd->u.cli.cl_target_uuid.uuid, + imp->imp_next_reconnect, + (long) (imp->imp_next_reconnect - get_seconds())); + + RETURN(0); +} + +#if 0 +static int +gss_svc_unseal_request(struct ptlrpc_request *req, + struct rsc *rsci, + struct gss_wire_cred *gc, + __u32 *vp, __u32 vlen) +{ + struct ptlrpcs_wire_hdr *sec_hdr; + struct gss_ctx *ctx = rsci->mechctx; + rawobj_t cipher_text, plain_text; + __u32 major; + ENTRY; + + sec_hdr = (struct ptlrpcs_wire_hdr *) req->rq_reqbuf; + + if (vlen < 4) { + CERROR("vlen only %u\n", vlen); + RETURN(GSS_S_CALL_BAD_STRUCTURE); + } + + cipher_text.len = le32_to_cpu(*vp++); + cipher_text.data = (__u8 *) vp; + vlen -= 4; + + if (cipher_text.len > vlen) { + CERROR("cipher claimed %u while buf only %u\n", + cipher_text.len, vlen); + RETURN(GSS_S_CALL_BAD_STRUCTURE); + } + + plain_text = cipher_text; + + major = lgss_unwrap(ctx, GSS_C_QOP_DEFAULT, &cipher_text, &plain_text); + if (major) { + CERROR("unwrap error 0x%x\n", major); + RETURN(major); + } + + if (gss_check_seq_num(&rsci->seqdata, gc->gc_seq)) { + CERROR("discard replayed request %p(o%u,x"LPU64",t"LPU64")\n", + req, req->rq_reqmsg->opc, req->rq_xid, + req->rq_reqmsg->transno); + RETURN(GSS_S_DUPLICATE_TOKEN); + } + + req->rq_reqmsg = (struct lustre_msg *) (vp); + req->rq_reqlen = plain_text.len; + + CDEBUG(D_SEC, "msg len %d\n", req->rq_reqlen); + + RETURN(GSS_S_COMPLETE); +} +#endif + +static +struct cache_deferred_req* cache_upcall_defer(struct cache_req *req) +{ + return NULL; +} +static struct cache_req cache_upcall_chandle = { cache_upcall_defer }; + +int gss_svc_upcall_handle_init(struct ptlrpc_request *req, + struct gss_svc_reqctx *grctx, + struct gss_wire_ctx *gw, + struct obd_device *target, + __u32 lustre_svc, + rawobj_t *rvs_hdl, + rawobj_t *in_token) +{ + struct ptlrpc_reply_state *rs; + struct rsc *rsci = NULL; + struct rsi *rsip = NULL, rsikey; + wait_queue_t wait; + int replen = sizeof(struct ptlrpc_body); + struct gss_rep_header *rephdr; + int first_check = 1; + int rc = SECSVC_DROP; + ENTRY; + + memset(&rsikey, 0, sizeof(rsikey)); + rsikey.lustre_svc = lustre_svc; + rsikey.nid = (__u64) req->rq_peer.nid; + + /* duplicate context handle. for INIT it always 0 */ + if (rawobj_dup(&rsikey.in_handle, &gw->gw_handle)) { + CERROR("fail to dup context handle\n"); + GOTO(out, rc); + } + + if (rawobj_dup(&rsikey.in_token, in_token)) { + CERROR("can't duplicate token\n"); + rawobj_free(&rsikey.in_handle); + GOTO(out, rc); + } + + rsip = rsi_lookup(&rsikey, 0); + rsi_free(&rsikey); + if (!rsip) { + CERROR("error in rsi_lookup.\n"); + + if (!gss_pack_err_notify(req, GSS_S_FAILURE, 0)) + rc = SECSVC_COMPLETE; + + GOTO(out, rc); + } + + cache_get(&rsip->h); /* take an extra ref */ + init_waitqueue_head(&rsip->waitq); + init_waitqueue_entry(&wait, current); + add_wait_queue(&rsip->waitq, &wait); + +cache_check: + /* Note each time cache_check() will drop a reference if return + * non-zero. We hold an extra reference on initial rsip, but must + * take care of following calls. + */ + rc = cache_check(&rsi_cache, &rsip->h, &cache_upcall_chandle); + switch (rc) { + case -EAGAIN: { + int valid; + + if (first_check) { + first_check = 0; + + read_lock(&rsi_cache.hash_lock); + valid = test_bit(CACHE_VALID, &rsip->h.flags); + if (valid == 0) + set_current_state(TASK_INTERRUPTIBLE); + read_unlock(&rsi_cache.hash_lock); + + if (valid == 0) { + unsigned long j1, j2; + + j1 = jiffies; + schedule_timeout(GSS_SVC_UPCALL_TIMEOUT * HZ); + j2 = jiffies; + CWARN("slept %lu ticks for cache refill\n", + j2 - j1); + } + + cache_get(&rsip->h); + goto cache_check; + } + CWARN("waited %ds timeout, drop\n", GSS_SVC_UPCALL_TIMEOUT); + break; + } + case -ENOENT: + CWARN("cache_check return ENOENT, drop\n"); + break; + case 0: + /* if not the first check, we have to release the extra + * reference we just added on it. + */ + if (!first_check) + cache_put(&rsip->h, &rsi_cache); + CDEBUG(D_SEC, "cache_check is good\n"); + break; + } + + remove_wait_queue(&rsip->waitq, &wait); + cache_put(&rsip->h, &rsi_cache); + + if (rc) + GOTO(out, rc = SECSVC_DROP); + + rc = SECSVC_DROP; + rsci = gss_svc_searchbyctx(&rsip->out_handle); + if (!rsci) { + CERROR("authentication failed\n"); + + if (!gss_pack_err_notify(req, GSS_S_FAILURE, 0)) + rc = SECSVC_COMPLETE; + + GOTO(out, rc); + } else { + cache_get(&rsci->h); + grctx->src_ctx = &rsci->ctx; + } + + if (rawobj_dup(&rsci->ctx.gsc_rvs_hdl, rvs_hdl)) { + CERROR("failed duplicate reverse handle\n"); + GOTO(out, rc); + } + + rsci->target = target; + + CWARN("server create rsc %p(%u->%s)\n", + rsci, rsci->ctx.gsc_uid, libcfs_nid2str(req->rq_peer.nid)); + + if (rsip->out_handle.len > PTLRPC_GSS_MAX_HANDLE_SIZE) { + CERROR("handle size %u too large\n", rsip->out_handle.len); + GOTO(out, rc = SECSVC_DROP); + } + + grctx->src_init = 1; + grctx->src_reserve_len = size_round4(rsip->out_token.len); + + rc = lustre_pack_reply_v2(req, 1, &replen, NULL); + if (rc) { + CERROR("failed to pack reply: %d\n", rc); + GOTO(out, rc = SECSVC_DROP); + } + + rs = req->rq_reply_state; + LASSERT(rs->rs_repbuf->lm_bufcount == 3); + LASSERT(rs->rs_repbuf->lm_buflens[0] >= + sizeof(*rephdr) + rsip->out_handle.len); + LASSERT(rs->rs_repbuf->lm_buflens[2] >= rsip->out_token.len); + + rephdr = lustre_msg_buf(rs->rs_repbuf, 0, 0); + rephdr->gh_version = PTLRPC_GSS_VERSION; + rephdr->gh_flags = 0; + rephdr->gh_proc = PTLRPC_GSS_PROC_ERR; + rephdr->gh_major = rsip->major_status; + rephdr->gh_minor = rsip->minor_status; + rephdr->gh_seqwin = GSS_SEQ_WIN; + rephdr->gh_handle.len = rsip->out_handle.len; + memcpy(rephdr->gh_handle.data, rsip->out_handle.data, + rsip->out_handle.len); + + memcpy(lustre_msg_buf(rs->rs_repbuf, 2, 0), rsip->out_token.data, + rsip->out_token.len); + + rs->rs_repdata_len = lustre_shrink_msg(rs->rs_repbuf, 2, + rsip->out_token.len, 0); + + if (rsci->ctx.gsc_usr_mds) + CWARN("user from %s authenticated as mds\n", + libcfs_nid2str(req->rq_peer.nid)); + + rc = SECSVC_OK; + +out: + /* it looks like here we should put rsip also, but this mess up + * with NFS cache mgmt code... FIXME + */ +#if 0 + if (rsip) + rsi_put(&rsip->h, &rsi_cache); +#endif + + if (rsci) { + /* if anything went wrong, we don't keep the context too */ + if (rc != SECSVC_OK) + set_bit(CACHE_NEGATIVE, &rsci->h.flags); + + rsc_put(&rsci->h, &rsc_cache); + } + RETURN(rc); +} + +struct gss_svc_ctx *gss_svc_upcall_get_ctx(struct ptlrpc_request *req, + struct gss_wire_ctx *gw) +{ + struct rsc *rsc; + + rsc = gss_svc_searchbyctx(&gw->gw_handle); + if (!rsc) { + CWARN("Invalid gss context handle from %s\n", + libcfs_nid2str(req->rq_peer.nid)); + return NULL; + } + + return &rsc->ctx; +} + +void gss_svc_upcall_put_ctx(struct gss_svc_ctx *ctx) +{ + struct rsc *rsc = container_of(ctx, struct rsc, ctx); + + rsc_put(&rsc->h, &rsc_cache); +} + +void gss_svc_upcall_destroy_ctx(struct gss_svc_ctx *ctx) +{ + struct rsc *rsc = container_of(ctx, struct rsc, ctx); + + set_bit(CACHE_NEGATIVE, &rsc->h.flags); +} + +int __init gss_svc_init_upcall(void) +{ + cache_register(&rsc_cache); + cache_register(&rsi_cache); + + return 0; +} + +void __exit gss_svc_exit_upcall(void) +{ + int rc; + + cache_purge(&rsi_cache); + if ((rc = cache_unregister(&rsi_cache))) + CERROR("unregister rsi cache: %d\n", rc); + + cache_purge(&rsc_cache); + if ((rc = cache_unregister(&rsc_cache))) + CERROR("unregister rsc cache: %d\n", rc); +} diff --git a/lustre/ptlrpc/gss/lproc_gss.c b/lustre/ptlrpc/gss/lproc_gss.c new file mode 100644 index 0000000..b32a0bd --- /dev/null +++ b/lustre/ptlrpc/gss/lproc_gss.c @@ -0,0 +1,98 @@ +/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*- + * vim:expandtab:shiftwidth=8:tabstop=8: + * + * Copyright (C) 2001-2003 Cluster File Systems, Inc. + * Author Peter Braam + * + * This file is part of the Lustre file system, http://www.lustre.org + * Lustre is a trademark of Cluster File Systems, Inc. + * + * You may have signed or agreed to another license before downloading + * this software. If so, you are bound by the terms and conditions + * of that agreement, and the following does not apply to you. See the + * LICENSE file included with this distribution for more information. + * + * If you did not agree to a different license, then this copy of Lustre + * is open source software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * In either case, Lustre is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * license text for more details. + * + */ + +#ifndef EXPORT_SYMTAB +# define EXPORT_SYMTAB +#endif +#define DEBUG_SUBSYSTEM S_SEC +#ifdef __KERNEL__ +#include +#include +#include +#include +#include +#include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gss_err.h" +#include "gss_internal.h" +#include "gss_api.h" + +static struct proc_dir_entry *gss_proc_root = NULL; + +static int gss_proc_write_secinit(struct file *file, const char *buffer, + unsigned long count, void *data) +{ + int rc; + + rc = gss_do_ctx_init_rpc((char *) buffer, count); + if (rc) { + LASSERT(rc < 0); + return rc; + } + + return ((int) count); +} + +static struct lprocfs_vars gss_lprocfs_vars[] = { + { "init_channel", NULL, gss_proc_write_secinit, NULL }, + { NULL } +}; + +int gss_init_lproc(void) +{ + int rc; + gss_proc_root = lprocfs_register("gss", proc_lustre_root, + gss_lprocfs_vars, NULL); + + if (IS_ERR(gss_proc_root)) { + rc = PTR_ERR(gss_proc_root); + gss_proc_root = NULL; + CERROR("failed to initialize lproc entries: %d\n", rc); + return rc; + } + + return 0; +} + +void gss_exit_lproc(void) +{ + if (gss_proc_root) { + lprocfs_remove(gss_proc_root); + gss_proc_root = NULL; + } +} diff --git a/lustre/ptlrpc/gss/sec_gss.c b/lustre/ptlrpc/gss/sec_gss.c new file mode 100644 index 0000000..21bc96f --- /dev/null +++ b/lustre/ptlrpc/gss/sec_gss.c @@ -0,0 +1,2266 @@ +/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*- + * vim:expandtab:shiftwidth=8:tabstop=8: + * + * Modifications for Lustre + * Copyright 2004 - 2006, Cluster File Systems, Inc. + * All rights reserved + * Author: Eric Mei + */ + +/* + * linux/net/sunrpc/auth_gss.c + * + * RPCSEC_GSS client authentication. + * + * Copyright (c) 2000 The Regents of the University of Michigan. + * All rights reserved. + * + * Dug Song + * Andy Adamson + * + * 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 EXPORT_SYMTAB +# define EXPORT_SYMTAB +#endif +#define DEBUG_SUBSYSTEM S_SEC +#ifdef __KERNEL__ +#include +#include +#include +#include +#include +#include +#include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "gss_err.h" +#include "gss_internal.h" +#include "gss_api.h" + +#include + +/* pre-definition */ +static struct ptlrpc_sec_policy gss_policy; +static struct ptlrpc_cli_ctx * gss_sec_create_ctx(struct ptlrpc_sec *sec, + struct vfs_cred *vcred); +static void gss_sec_destroy_ctx(struct ptlrpc_sec *sec, + struct ptlrpc_cli_ctx *ctx); +/******************************************** + * wire data swabber * + ********************************************/ + +static +void gss_header_swabber(struct gss_header *ghdr) +{ + __swab32s(&ghdr->gh_version); + __swab32s(&ghdr->gh_flags); + __swab32s(&ghdr->gh_proc); + __swab32s(&ghdr->gh_seq); + __swab32s(&ghdr->gh_svc); + __swab32s(&ghdr->gh_pad1); + __swab32s(&ghdr->gh_pad2); + __swab32s(&ghdr->gh_pad3); + __swab32s(&ghdr->gh_handle.len); +} + +struct gss_header *gss_swab_header(struct lustre_msg *msg, int segment) +{ + struct gss_header *ghdr; + + ghdr = lustre_swab_buf(msg, segment, sizeof(*ghdr), + gss_header_swabber); + + if (ghdr && + sizeof(*ghdr) + ghdr->gh_handle.len > msg->lm_buflens[segment]) { + CERROR("gss header require length %d, now %d received\n", + sizeof(*ghdr) + ghdr->gh_handle.len, + msg->lm_buflens[segment]); + return NULL; + } + + return ghdr; +} + +static +void gss_netobj_swabber(netobj_t *obj) +{ + __swab32s(&obj->len); +} + +netobj_t *gss_swab_netobj(struct lustre_msg *msg, int segment) +{ + netobj_t *obj; + + obj = lustre_swab_buf(msg, segment, sizeof(*obj), gss_netobj_swabber); + if (obj && sizeof(*obj) + obj->len > msg->lm_buflens[segment]) { + CERROR("netobj require length %d but only %d received\n", + sizeof(*obj) + obj->len, msg->lm_buflens[segment]); + return NULL; + } + + return obj; +} + +/* + * payload should be obtained from mechanism. but currently since we + * only support kerberos, we could simply use fixed value. + * krb5 header: 16 + * krb5 checksum: 20 + */ +#define GSS_KRB5_INTEG_MAX_PAYLOAD (40) + +static inline +int gss_estimate_payload(struct gss_ctx *mechctx, int msgsize, int privacy) +{ + if (privacy) { + /* we suppose max cipher block size is 16 bytes. here we + * add 16 for confounder and 16 for padding. + */ + return GSS_KRB5_INTEG_MAX_PAYLOAD + msgsize + 16 + 16 + 16; + } else { + return GSS_KRB5_INTEG_MAX_PAYLOAD; + } +} + +/* + * return signature size, otherwise < 0 to indicate error + */ +static +int gss_sign_msg(struct lustre_msg *msg, + struct gss_ctx *mechctx, + __u32 proc, __u32 seq, + rawobj_t *handle) +{ + struct gss_header *ghdr; + rawobj_t text[3], mic; + int textcnt, mic_idx = msg->lm_bufcount - 1; + __u32 major; + + LASSERT(msg->lm_bufcount >= 3); + + /* gss hdr */ + LASSERT(msg->lm_buflens[0] >= + sizeof(*ghdr) + (handle ? handle->len : 0)); + ghdr = lustre_msg_buf(msg, 0, 0); + + ghdr->gh_version = PTLRPC_GSS_VERSION; + ghdr->gh_flags = 0; + ghdr->gh_proc = proc; + ghdr->gh_seq = seq; + ghdr->gh_svc = PTLRPC_GSS_SVC_INTEGRITY; + if (!handle) { + /* fill in a fake one */ + ghdr->gh_handle.len = 0; + } else { + ghdr->gh_handle.len = handle->len; + memcpy(ghdr->gh_handle.data, handle->data, handle->len); + } + + /* MIC */ + for (textcnt = 0; textcnt < mic_idx; textcnt++) { + text[textcnt].len = msg->lm_buflens[textcnt]; + text[textcnt].data = lustre_msg_buf(msg, textcnt, 0); + } + + mic.len = msg->lm_buflens[mic_idx]; + mic.data = lustre_msg_buf(msg, mic_idx, 0); + + major = lgss_get_mic(mechctx, textcnt, text, &mic); + if (major != GSS_S_COMPLETE) { + CERROR("fail to generate MIC: %08x\n", major); + return -EPERM; + } + LASSERT(mic.len <= msg->lm_buflens[mic_idx]); + + return lustre_shrink_msg(msg, mic_idx, mic.len, 0); +} + +/* + * return gss error + */ +static +__u32 gss_verify_msg(struct lustre_msg *msg, + struct gss_ctx *mechctx) +{ + rawobj_t text[3]; + rawobj_t mic; + int textcnt, mic_idx = msg->lm_bufcount - 1; + __u32 major; + + for (textcnt = 0; textcnt < mic_idx; textcnt++) { + text[textcnt].len = msg->lm_buflens[textcnt]; + text[textcnt].data = lustre_msg_buf(msg, textcnt, 0); + } + + mic.len = msg->lm_buflens[mic_idx]; + mic.data = lustre_msg_buf(msg, mic_idx, 0); + + major = lgss_verify_mic(mechctx, textcnt, text, &mic); + if (major != GSS_S_COMPLETE) + CERROR("mic verify error: %08x\n", major); + + return major; +} + +/* + * return gss error code + */ +static +__u32 gss_unseal_msg(struct gss_ctx *mechctx, + struct lustre_msg *msgbuf, + int *msg_len, int msgbuf_len) +{ + rawobj_t clear_obj, micobj, msgobj, token; + __u8 *clear_buf; + int clear_buflen; + __u32 major; + ENTRY; + + if (msgbuf->lm_bufcount != 3) { + CERROR("invalid bufcount %d\n", msgbuf->lm_bufcount); + RETURN(GSS_S_FAILURE); + } + + /* verify gss header */ + msgobj.len = msgbuf->lm_buflens[0]; + msgobj.data = lustre_msg_buf(msgbuf, 0, 0); + micobj.len = msgbuf->lm_buflens[1]; + micobj.data = lustre_msg_buf(msgbuf, 1, 0); + + major = lgss_verify_mic(mechctx, 1, &msgobj, &micobj); + if (major != GSS_S_COMPLETE) { + CERROR("priv: mic verify error: %08x\n", major); + RETURN(major); + } + + /* temporary clear text buffer */ + clear_buflen = msgbuf->lm_buflens[2]; + OBD_ALLOC(clear_buf, clear_buflen); + if (!clear_buf) + RETURN(GSS_S_FAILURE); + + token.len = msgbuf->lm_buflens[2]; + token.data = lustre_msg_buf(msgbuf, 2, 0); + + clear_obj.len = clear_buflen; + clear_obj.data = clear_buf; + + major = lgss_unwrap(mechctx, &token, &clear_obj); + if (major != GSS_S_COMPLETE) { + CERROR("priv: unwrap message error: %08x\n", major); + GOTO(out_free, major = GSS_S_FAILURE); + } + LASSERT(clear_obj.len <= clear_buflen); + + /* now the decrypted message */ + memcpy(msgbuf, clear_obj.data, clear_obj.len); + *msg_len = clear_obj.len; + + major = GSS_S_COMPLETE; +out_free: + OBD_FREE(clear_buf, clear_buflen); + RETURN(major); +} + +/******************************************** + * gss client context manipulation helpers * + ********************************************/ + +void gss_cli_ctx_uptodate(struct gss_cli_ctx *gctx) +{ + struct ptlrpc_cli_ctx *ctx = &gctx->gc_base; + unsigned long ctx_expiry; + + if (lgss_inquire_context(gctx->gc_mechctx, &ctx_expiry)) { + CERROR("ctx %p(%u): unable to inquire, expire it now\n", + gctx, ctx->cc_vcred.vc_uid); + ctx_expiry = 1; /* make it expired now */ + } + + ctx->cc_expire = gss_round_ctx_expiry(ctx_expiry, + ctx->cc_sec->ps_flags); + + /* At this point this ctx might have been marked as dead by + * someone else, in which case nobody will make further use + * of it. we don't care, and mark it UPTODATE will help + * destroying server side context when it be destroied. + */ + set_bit(PTLRPC_CTX_UPTODATE_BIT, &ctx->cc_flags); + + CWARN("%s ctx %p(%u->%s), will expire at %lu(%lds lifetime)\n", + (ctx->cc_sec->ps_flags & PTLRPC_SEC_FL_REVERSE ? + "server installed reverse" : "client refreshed"), + ctx, ctx->cc_vcred.vc_uid, sec2target_str(ctx->cc_sec), + ctx->cc_expire, (long) (ctx->cc_expire - get_seconds())); +} + +static +void gss_cli_ctx_finalize(struct gss_cli_ctx *gctx) +{ + if (gctx->gc_mechctx) + lgss_delete_sec_context(&gctx->gc_mechctx); + + rawobj_free(&gctx->gc_handle); +} + +/* + * Based on sequence number algorithm as specified in RFC 2203. + * + * modified for our own problem: arriving request has valid sequence number, + * but unwrapping request might cost a long time, after that its sequence + * are not valid anymore (fall behind the window). It rarely happen, mostly + * under extreme load. + * + * note we should not check sequence before verify the integrity of incoming + * request, because just one attacking request with high sequence number might + * cause all following request be dropped. + * + * so here we use a multi-phase approach: prepare 2 sequence windows, + * "main window" for normal sequence and "back window" for fall behind sequence. + * and 3-phase checking mechanism: + * 0 - before integrity verification, perform a initial sequence checking in + * main window, which only try and don't actually set any bits. if the + * sequence is high above the window or fit in the window and the bit + * is 0, then accept and proceed to integrity verification. otherwise + * reject this sequence. + * 1 - after integrity verification, check in main window again. if this + * sequence is high above the window or fit in the window and the bit + * is 0, then set the bit and accept; if it fit in the window but bit + * already set, then reject; if it fall behind the window, then proceed + * to phase 2. + * 2 - check in back window. if it is high above the window or fit in the + * window and the bit is 0, then set the bit and accept. otherwise reject. + * + * note phase 0 is necessary, because otherwise replay attacking request of + * sequence which between the 2 windows can't be detected. + * + * this mechanism can't totally solve the problem, but could help much less + * number of valid requests be dropped. + */ +static +int gss_do_check_seq(unsigned long *window, __u32 win_size, __u32 *max_seq, + __u32 seq_num, int phase) +{ + LASSERT(phase >= 0 && phase <= 2); + + if (seq_num > *max_seq) { + /* + * 1. high above the window + */ + if (phase == 0) + return 0; + + if (seq_num >= *max_seq + win_size) { + memset(window, 0, win_size / 8); + *max_seq = seq_num; + } else { + while(*max_seq < seq_num) { + (*max_seq)++; + __clear_bit((*max_seq) % win_size, window); + } + } + __set_bit(seq_num % win_size, window); + } else if (seq_num + win_size <= *max_seq) { + /* + * 2. low behind the window + */ + if (phase == 0 || phase == 2) + goto replay; + + CWARN("seq %u is %u behind (size %d), check backup window\n", + seq_num, *max_seq - win_size - seq_num, win_size); + return 1; + } else { + /* + * 3. fit into the window + */ + switch (phase) { + case 0: + if (test_bit(seq_num % win_size, window)) + goto replay; + break; + case 1: + case 2: + if (__test_and_set_bit(seq_num % win_size, window)) + goto replay; + break; + } + } + + return 0; + +replay: + CERROR("seq %u (%s %s window) is a replay: max %u, winsize %d\n", + seq_num, + seq_num + win_size > *max_seq ? "in" : "behind", + phase == 2 ? "backup " : "main", + *max_seq, win_size); + return 1; +} + +/* + * Based on sequence number algorithm as specified in RFC 2203. + * + * if @set == 0: initial check, don't set any bit in window + * if @sec == 1: final check, set bit in window + */ +int gss_check_seq_num(struct gss_svc_seq_data *ssd, __u32 seq_num, int set) +{ + int rc = 0; + + spin_lock(&ssd->ssd_lock); + + if (set == 0) { + rc = gss_do_check_seq(ssd->ssd_win_main, GSS_SEQ_WIN_MAIN, + &ssd->ssd_max_main, seq_num, 0); + } else { + rc = gss_do_check_seq(ssd->ssd_win_main, GSS_SEQ_WIN_MAIN, + &ssd->ssd_max_main, seq_num, 1); + if (rc == 0) + goto exit; + rc = gss_do_check_seq(ssd->ssd_win_back, GSS_SEQ_WIN_BACK, + &ssd->ssd_max_back, seq_num, 2); + } +exit: + spin_unlock(&ssd->ssd_lock); + return rc; +} + +/*************************************** + * cred APIs * + ***************************************/ + +static inline +int gss_cli_payload(struct ptlrpc_cli_ctx *ctx, + int msgsize, int privacy) +{ + return gss_estimate_payload(NULL, msgsize, privacy); +} + +static +int gss_cli_ctx_refresh(struct ptlrpc_cli_ctx *ctx) +{ + /* if we are refreshing for root, also update the reverse + * handle index, do not confuse reverse contexts. + */ + if (ctx->cc_vcred.vc_uid == 0) { + struct gss_sec *gsec; + + gsec = container_of(ctx->cc_sec, struct gss_sec, gs_base); + gsec->gs_rvs_hdl = gss_get_next_ctx_index(); + } + + return gss_ctx_refresh_pipefs(ctx); +} + +static +int gss_cli_ctx_match(struct ptlrpc_cli_ctx *ctx, + struct vfs_cred *vcred) +{ + return (ctx->cc_vcred.vc_uid == vcred->vc_uid); +} + +static +int gss_cli_ctx_sign(struct ptlrpc_cli_ctx *ctx, + struct ptlrpc_request *req) +{ + struct gss_cli_ctx *gctx; + __u32 seq; + int rc; + ENTRY; + + LASSERT(req->rq_reqbuf); + LASSERT(req->rq_reqbuf->lm_bufcount >= 3); + LASSERT(req->rq_cli_ctx == ctx); + + /* nothing to do for context negotiation RPCs */ + if (req->rq_ctx_init) + RETURN(0); + + gctx = container_of(ctx, struct gss_cli_ctx, gc_base); +redo: + seq = atomic_inc_return(&gctx->gc_seq); + + rc = gss_sign_msg(req->rq_reqbuf, gctx->gc_mechctx, + gctx->gc_proc, seq, &gctx->gc_handle); + if (rc < 0) + RETURN(rc); + + /* gss_sign_msg() msg might take long time to finish, in which period + * more rpcs could be wrapped up and sent out. if we found too many + * of them we should repack this rpc, because sent it too late might + * lead to the sequence number fall behind the window on server and + * be dropped. also applies to gss_cli_ctx_seal(). + */ + if (atomic_read(&gctx->gc_seq) - seq > GSS_SEQ_REPACK_THRESHOLD) { + CWARN("req %p: %u behind, retry signing\n", + req, atomic_read(&gctx->gc_seq) - seq); + goto redo; + } + + req->rq_reqdata_len = rc; + RETURN(0); +} + +static +int gss_cli_ctx_handle_err_notify(struct ptlrpc_cli_ctx *ctx, + struct ptlrpc_request *req, + struct gss_header *ghdr) +{ + struct gss_err_header *errhdr; + int rc; + + LASSERT(ghdr->gh_proc == PTLRPC_GSS_PROC_ERR); + + errhdr = (struct gss_err_header *) ghdr; + + /* server return NO_CONTEXT might be caused by context expire + * or server reboot/failover. we refresh the cred transparently + * to upper layer. + * In some cases, our gss handle is possible to be incidentally + * identical to another handle since the handle itself is not + * fully random. In krb5 case, the GSS_S_BAD_SIG will be + * returned, maybe other gss error for other mechanism. + * + * if we add new mechanism, make sure the correct error are + * returned in this case. + * + * but in any cases, don't resend ctx destroying rpc, don't resend + * reverse rpc. + */ + if (req->rq_ctx_fini) { + CWARN("server respond error (%08x/%08x) for ctx fini\n", + errhdr->gh_major, errhdr->gh_minor); + rc = -EINVAL; + } else if (ctx->cc_sec->ps_flags & PTLRPC_SEC_FL_REVERSE) { + CWARN("reverse server respond error (%08x/%08x)\n", + errhdr->gh_major, errhdr->gh_minor); + rc = -EINVAL; + } else if (errhdr->gh_major == GSS_S_NO_CONTEXT || + errhdr->gh_major == GSS_S_BAD_SIG) { + CWARN("req x"LPU64"/t"LPU64": server respond ctx %p(%u->%s) " + "%s, server might lost the context.\n", + req->rq_xid, req->rq_transno, ctx, ctx->cc_vcred.vc_uid, + sec2target_str(ctx->cc_sec), + errhdr->gh_major == GSS_S_NO_CONTEXT ? + "NO_CONTEXT" : "BAD_SIG"); + + sptlrpc_ctx_expire(ctx); + req->rq_resend = 1; + rc = 0; + } else { + CERROR("req %p: server report gss error (%x/%x)\n", + req, errhdr->gh_major, errhdr->gh_minor); + rc = -EACCES; + } + + return rc; +} + +static +int gss_cli_ctx_verify(struct ptlrpc_cli_ctx *ctx, + struct ptlrpc_request *req) +{ + struct gss_cli_ctx *gctx; + struct gss_header *ghdr, *reqhdr; + struct lustre_msg *msg = req->rq_repbuf; + __u32 major; + int rc = 0; + ENTRY; + + LASSERT(req->rq_cli_ctx == ctx); + LASSERT(msg); + + req->rq_repdata_len = req->rq_nob_received; + gctx = container_of(ctx, struct gss_cli_ctx, gc_base); + + /* special case for context negotiation, rq_repmsg/rq_replen actually + * are not used currently. + */ + if (req->rq_ctx_init) { + req->rq_repmsg = lustre_msg_buf(msg, 1, 0); + req->rq_replen = msg->lm_buflens[1]; + RETURN(0); + } + + if (msg->lm_bufcount < 3 || msg->lm_bufcount > 4) { + CERROR("unexpected bufcount %u\n", msg->lm_bufcount); + RETURN(-EPROTO); + } + + ghdr = gss_swab_header(msg, 0); + if (ghdr == NULL) { + CERROR("can't decode gss header\n"); + RETURN(-EPROTO); + } + + /* sanity checks */ + reqhdr = lustre_msg_buf(msg, 0, sizeof(*reqhdr)); + LASSERT(reqhdr); + + if (ghdr->gh_version != reqhdr->gh_version) { + CERROR("gss version %u mismatch, expect %u\n", + ghdr->gh_version, reqhdr->gh_version); + RETURN(-EPROTO); + } + + switch (ghdr->gh_proc) { + case PTLRPC_GSS_PROC_DATA: + if (ghdr->gh_seq != reqhdr->gh_seq) { + CERROR("seqnum %u mismatch, expect %u\n", + ghdr->gh_seq, reqhdr->gh_seq); + RETURN(-EPROTO); + } + + if (ghdr->gh_svc != PTLRPC_GSS_SVC_INTEGRITY) { + CERROR("unexpected svc %d\n", ghdr->gh_svc); + RETURN(-EPROTO); + } + + if (lustre_msg_swabbed(msg)) + gss_header_swabber(ghdr); + + major = gss_verify_msg(msg, gctx->gc_mechctx); + if (major != GSS_S_COMPLETE) + RETURN(-EPERM); + + req->rq_repmsg = lustre_msg_buf(msg, 1, 0); + req->rq_replen = msg->lm_buflens[1]; + + if (SEC_FLAVOR_HAS_BULK(req->rq_sec_flavor)) { + if (msg->lm_bufcount < 4) { + CERROR("Invalid reply bufcount %u\n", + msg->lm_bufcount); + RETURN(-EPROTO); + } + + /* bulk checksum is the second last segment */ + rc = bulk_sec_desc_unpack(msg, msg->lm_bufcount - 2); + } + break; + case PTLRPC_GSS_PROC_ERR: + rc = gss_cli_ctx_handle_err_notify(ctx, req, ghdr); + break; + default: + CERROR("unknown gss proc %d\n", ghdr->gh_proc); + rc = -EPROTO; + } + + RETURN(rc); +} + +static +int gss_cli_ctx_seal(struct ptlrpc_cli_ctx *ctx, + struct ptlrpc_request *req) +{ + struct gss_cli_ctx *gctx; + rawobj_t msgobj, cipher_obj, micobj; + struct gss_header *ghdr; + int buflens[3], wiresize, rc; + __u32 major; + ENTRY; + + LASSERT(req->rq_clrbuf); + LASSERT(req->rq_cli_ctx == ctx); + LASSERT(req->rq_reqlen); + + gctx = container_of(ctx, struct gss_cli_ctx, gc_base); + + /* close clear data length */ + req->rq_clrdata_len = lustre_msg_size_v2(req->rq_clrbuf->lm_bufcount, + req->rq_clrbuf->lm_buflens); + + /* calculate wire data length */ + buflens[0] = PTLRPC_GSS_HEADER_SIZE; + buflens[1] = gss_cli_payload(&gctx->gc_base, buflens[0], 0); + buflens[2] = gss_cli_payload(&gctx->gc_base, req->rq_clrdata_len, 1); + wiresize = lustre_msg_size_v2(3, buflens); + + /* allocate wire buffer */ + if (req->rq_pool) { + /* pre-allocated */ + LASSERT(req->rq_reqbuf); + LASSERT(req->rq_reqbuf != req->rq_clrbuf); + LASSERT(req->rq_reqbuf_len >= wiresize); + } else { + OBD_ALLOC(req->rq_reqbuf, wiresize); + if (!req->rq_reqbuf) + RETURN(-ENOMEM); + req->rq_reqbuf_len = wiresize; + } + + lustre_init_msg_v2(req->rq_reqbuf, 3, buflens, NULL); + req->rq_reqbuf->lm_secflvr = req->rq_sec_flavor; + + /* gss header */ + ghdr = lustre_msg_buf(req->rq_reqbuf, 0, 0); + ghdr->gh_version = PTLRPC_GSS_VERSION; + ghdr->gh_flags = 0; + ghdr->gh_proc = gctx->gc_proc; + ghdr->gh_seq = atomic_inc_return(&gctx->gc_seq); + ghdr->gh_svc = PTLRPC_GSS_SVC_PRIVACY; + ghdr->gh_handle.len = gctx->gc_handle.len; + memcpy(ghdr->gh_handle.data, gctx->gc_handle.data, gctx->gc_handle.len); + +redo: + /* header signature */ + msgobj.len = req->rq_reqbuf->lm_buflens[0]; + msgobj.data = lustre_msg_buf(req->rq_reqbuf, 0, 0); + micobj.len = req->rq_reqbuf->lm_buflens[1]; + micobj.data = lustre_msg_buf(req->rq_reqbuf, 1, 0); + + major = lgss_get_mic(gctx->gc_mechctx, 1, &msgobj, &micobj); + if (major != GSS_S_COMPLETE) { + CERROR("priv: sign message error: %08x\n", major); + GOTO(err_free, rc = -EPERM); + } + /* perhaps shrink msg has potential problem in re-packing??? + * ship a little bit more data is fine. + lustre_shrink_msg(req->rq_reqbuf, 1, micobj.len, 0); + */ + + /* clear text */ + msgobj.len = req->rq_clrdata_len; + msgobj.data = (__u8 *) req->rq_clrbuf; + + /* cipher text */ + cipher_obj.len = req->rq_reqbuf->lm_buflens[2]; + cipher_obj.data = lustre_msg_buf(req->rq_reqbuf, 2, 0); + + major = lgss_wrap(gctx->gc_mechctx, &msgobj, req->rq_clrbuf_len, + &cipher_obj); + if (major != GSS_S_COMPLETE) { + CERROR("priv: wrap message error: %08x\n", major); + GOTO(err_free, rc = -EPERM); + } + LASSERT(cipher_obj.len <= buflens[2]); + + /* see explain in gss_cli_ctx_sign() */ + if (atomic_read(&gctx->gc_seq) - ghdr->gh_seq > + GSS_SEQ_REPACK_THRESHOLD) { + CWARN("req %p: %u behind, retry sealing\n", + req, atomic_read(&gctx->gc_seq) - ghdr->gh_seq); + ghdr->gh_seq = atomic_inc_return(&gctx->gc_seq); + goto redo; + } + + /* now set the final wire data length */ + req->rq_reqdata_len = lustre_shrink_msg(req->rq_reqbuf, 2, + cipher_obj.len, 0); + + RETURN(0); + +err_free: + if (!req->rq_pool) { + OBD_FREE(req->rq_reqbuf, req->rq_reqbuf_len); + req->rq_reqbuf = NULL; + req->rq_reqbuf_len = 0; + } + RETURN(rc); +} + +static +int gss_cli_ctx_unseal(struct ptlrpc_cli_ctx *ctx, + struct ptlrpc_request *req) +{ + struct gss_cli_ctx *gctx; + struct gss_header *ghdr; + int msglen, rc; + __u32 major; + ENTRY; + + LASSERT(req->rq_repbuf); + LASSERT(req->rq_cli_ctx == ctx); + + gctx = container_of(ctx, struct gss_cli_ctx, gc_base); + + ghdr = gss_swab_header(req->rq_repbuf, 0); + if (ghdr == NULL) { + CERROR("can't decode gss header\n"); + RETURN(-EPROTO); + } + + /* sanity checks */ + if (ghdr->gh_version != PTLRPC_GSS_VERSION) { + CERROR("gss version %u mismatch, expect %u\n", + ghdr->gh_version, PTLRPC_GSS_VERSION); + RETURN(-EPROTO); + } + + switch (ghdr->gh_proc) { + case PTLRPC_GSS_PROC_DATA: + if (lustre_msg_swabbed(req->rq_repbuf)) + gss_header_swabber(ghdr); + + major = gss_unseal_msg(gctx->gc_mechctx, req->rq_repbuf, + &msglen, req->rq_repbuf_len); + if (major != GSS_S_COMPLETE) { + rc = -EPERM; + break; + } + + if (lustre_unpack_msg(req->rq_repbuf, msglen)) { + CERROR("Failed to unpack after decryption\n"); + RETURN(-EPROTO); + } + req->rq_repdata_len = msglen; + + if (req->rq_repbuf->lm_bufcount < 1) { + CERROR("Invalid reply buffer: empty\n"); + RETURN(-EPROTO); + } + + if (SEC_FLAVOR_HAS_BULK(req->rq_sec_flavor)) { + if (req->rq_repbuf->lm_bufcount < 2) { + CERROR("Too few request buffer segments %d\n", + req->rq_repbuf->lm_bufcount); + RETURN(-EPROTO); + } + + /* bulk checksum is the last segment */ + if (bulk_sec_desc_unpack(req->rq_repbuf, + req->rq_repbuf->lm_bufcount-1)) + RETURN(-EPROTO); + } + + req->rq_repmsg = lustre_msg_buf(req->rq_repbuf, 0, 0); + req->rq_replen = req->rq_repbuf->lm_buflens[0]; + + rc = 0; + break; + case PTLRPC_GSS_PROC_ERR: + rc = gss_cli_ctx_handle_err_notify(ctx, req, ghdr); + break; + default: + CERROR("unexpected proc %d\n", ghdr->gh_proc); + rc = -EPERM; + } + + RETURN(rc); +} + +static struct ptlrpc_ctx_ops gss_ctxops = { + .refresh = gss_cli_ctx_refresh, + .match = gss_cli_ctx_match, + .sign = gss_cli_ctx_sign, + .verify = gss_cli_ctx_verify, + .seal = gss_cli_ctx_seal, + .unseal = gss_cli_ctx_unseal, + .wrap_bulk = gss_cli_ctx_wrap_bulk, + .unwrap_bulk = gss_cli_ctx_unwrap_bulk, +}; + +/********************************************* + * reverse context installation * + *********************************************/ +static +int gss_install_rvs_cli_ctx(struct gss_sec *gsec, + struct ptlrpc_svc_ctx *svc_ctx) +{ + struct vfs_cred vcred; + struct gss_svc_reqctx *grctx; + struct ptlrpc_cli_ctx *cli_ctx; + struct gss_cli_ctx *cli_gctx; + struct gss_ctx *mechctx = NULL; + __u32 major; + int rc; + ENTRY; + + vcred.vc_uid = 0; + + cli_ctx = gss_sec_create_ctx(&gsec->gs_base, &vcred); + if (!cli_ctx) + RETURN(-ENOMEM); + + grctx = container_of(svc_ctx, struct gss_svc_reqctx, src_base); + LASSERT(grctx); + LASSERT(grctx->src_ctx); + LASSERT(grctx->src_ctx->gsc_mechctx); + + major = lgss_copy_reverse_context(grctx->src_ctx->gsc_mechctx, &mechctx); + if (major != GSS_S_COMPLETE) + GOTO(err_ctx, rc = -ENOMEM); + + cli_gctx = container_of(cli_ctx, struct gss_cli_ctx, gc_base); + + cli_gctx->gc_proc = PTLRPC_GSS_PROC_DATA; + cli_gctx->gc_win = GSS_SEQ_WIN; + atomic_set(&cli_gctx->gc_seq, 0); + + if (rawobj_dup(&cli_gctx->gc_handle, &grctx->src_ctx->gsc_rvs_hdl)) + GOTO(err_mechctx, rc = -ENOMEM); + + cli_gctx->gc_mechctx = mechctx; + gss_cli_ctx_uptodate(cli_gctx); + + sptlrpc_ctx_replace(&gsec->gs_base, cli_ctx); + RETURN(0); + +err_mechctx: + lgss_delete_sec_context(&mechctx); +err_ctx: + gss_sec_destroy_ctx(cli_ctx->cc_sec, cli_ctx); + return rc; +} + + +static inline +int gss_install_rvs_svc_ctx(struct obd_import *imp, + struct gss_sec *gsec, + struct gss_cli_ctx *gctx) +{ + return gss_svc_upcall_install_rvs_ctx(imp, gsec, gctx); +} + +/********************************************* + * GSS security APIs * + *********************************************/ + +static +struct ptlrpc_cli_ctx * gss_sec_create_ctx(struct ptlrpc_sec *sec, + struct vfs_cred *vcred) +{ + struct gss_cli_ctx *gctx; + struct ptlrpc_cli_ctx *ctx; + ENTRY; + + OBD_ALLOC(gctx, sizeof(*gctx)); + if (!gctx) + RETURN(NULL); + + gctx->gc_win = 0; + atomic_set(&gctx->gc_seq, 0); + + ctx = &gctx->gc_base; + INIT_HLIST_NODE(&ctx->cc_hash); + atomic_set(&ctx->cc_refcount, 0); + ctx->cc_sec = sec; + ctx->cc_ops = &gss_ctxops; + ctx->cc_expire = 0; + ctx->cc_flags = 0; + ctx->cc_vcred = *vcred; + spin_lock_init(&ctx->cc_lock); + INIT_LIST_HEAD(&ctx->cc_req_list); + + CDEBUG(D_SEC, "create a gss cred at %p(uid %u)\n", ctx, vcred->vc_uid); + RETURN(ctx); +} + +static +void gss_sec_destroy_ctx(struct ptlrpc_sec *sec, struct ptlrpc_cli_ctx *ctx) +{ + struct gss_cli_ctx *gctx; + ENTRY; + + LASSERT(ctx); + LASSERT(atomic_read(&ctx->cc_refcount) == 0); + + gctx = container_of(ctx, struct gss_cli_ctx, gc_base); + if (gctx->gc_mechctx) { + gss_do_ctx_fini_rpc(gctx); + gss_cli_ctx_finalize(gctx); + } + + CWARN("%s@%p: destroy ctx %p(%u->%s)\n", + ctx->cc_sec->ps_policy->sp_name, ctx->cc_sec, + ctx, ctx->cc_vcred.vc_uid, sec2target_str(ctx->cc_sec)); + + OBD_FREE(gctx, sizeof(*gctx)); + EXIT; +} + +#define GSS_CCACHE_SIZE (32) + +static +struct ptlrpc_sec* gss_sec_create(struct obd_import *imp, + struct ptlrpc_svc_ctx *ctx, + __u32 flavor, + unsigned long flags) +{ + struct gss_sec *gsec; + struct ptlrpc_sec *sec; + int alloc_size, cache_size, i; + ENTRY; + + LASSERT(imp); + LASSERT(SEC_FLAVOR_POLICY(flavor) == SPTLRPC_POLICY_GSS); + + if (ctx || flags & (PTLRPC_SEC_FL_ROOTONLY | PTLRPC_SEC_FL_REVERSE)) + cache_size = 1; + else + cache_size = GSS_CCACHE_SIZE; + + alloc_size = sizeof(*gsec) + sizeof(struct list_head) * cache_size; + + OBD_ALLOC(gsec, alloc_size); + if (!gsec) + RETURN(NULL); + + gsec->gs_mech = lgss_subflavor_to_mech(SEC_FLAVOR_SUB(flavor)); + if (!gsec->gs_mech) { + CERROR("gss backend 0x%x not found\n", SEC_FLAVOR_SUB(flavor)); + goto err_free; + } + + spin_lock_init(&gsec->gs_lock); + gsec->gs_rvs_hdl = 0ULL; /* will be updated later */ + + sec = &gsec->gs_base; + sec->ps_policy = &gss_policy; + sec->ps_flavor = flavor; + sec->ps_flags = flags; + sec->ps_import = class_import_get(imp); + sec->ps_lock = SPIN_LOCK_UNLOCKED; + sec->ps_ccache_size = cache_size; + sec->ps_ccache = (struct hlist_head *) (gsec + 1); + atomic_set(&sec->ps_busy, 0); + + for (i = 0; i < cache_size; i++) + INIT_HLIST_HEAD(&sec->ps_ccache[i]); + + if (!ctx) { + if (gss_sec_upcall_init(gsec)) + goto err_mech; + + sec->ps_gc_interval = 30 * 60; /* 30 minutes */ + sec->ps_gc_next = cfs_time_current_sec() + sec->ps_gc_interval; + } else { + LASSERT(sec->ps_flags & PTLRPC_SEC_FL_REVERSE); + + if (gss_install_rvs_cli_ctx(gsec, ctx)) + goto err_mech; + + /* never do gc on reverse sec */ + sec->ps_gc_interval = 0; + sec->ps_gc_next = 0; + } + + CWARN("create %s%s@%p\n", (ctx ? "reverse " : ""), + gss_policy.sp_name, gsec); + RETURN(sec); + +err_mech: + lgss_mech_put(gsec->gs_mech); +err_free: + OBD_FREE(gsec, alloc_size); + RETURN(NULL); +} + +static +void gss_sec_destroy(struct ptlrpc_sec *sec) +{ + struct gss_sec *gsec; + ENTRY; + + gsec = container_of(sec, struct gss_sec, gs_base); + CWARN("destroy %s@%p\n", gss_policy.sp_name, gsec); + + LASSERT(gsec->gs_mech); + LASSERT(sec->ps_import); + LASSERT(sec->ps_ccache); + LASSERT(sec->ps_ccache_size); + LASSERT(atomic_read(&sec->ps_refcount) == 0); + LASSERT(atomic_read(&sec->ps_busy) == 0); + + gss_sec_upcall_cleanup(gsec); + lgss_mech_put(gsec->gs_mech); + + class_import_put(sec->ps_import); + + OBD_FREE(gsec, sizeof(*gsec) + + sizeof(struct list_head) * sec->ps_ccache_size); + EXIT; +} + +static +int gss_alloc_reqbuf_auth(struct ptlrpc_sec *sec, + struct ptlrpc_request *req, + int msgsize) +{ + struct sec_flavor_config *conf; + int bufsize, txtsize; + int buflens[5], bufcnt = 2; + ENTRY; + + /* + * - gss header + * - lustre message + * - user descriptor + * - bulk sec descriptor + * - signature + */ + buflens[0] = PTLRPC_GSS_HEADER_SIZE; + buflens[1] = msgsize; + txtsize = buflens[0] + buflens[1]; + + if (SEC_FLAVOR_HAS_USER(req->rq_sec_flavor)) { + buflens[bufcnt] = sptlrpc_user_desc_size(); + txtsize += buflens[bufcnt]; + bufcnt++; + } + + if (SEC_FLAVOR_HAS_BULK(req->rq_sec_flavor)) { + conf = &req->rq_import->imp_obd->u.cli.cl_sec_conf; + buflens[bufcnt] = bulk_sec_desc_size(conf->sfc_bulk_csum, 1, + req->rq_bulk_read); + txtsize += buflens[bufcnt]; + bufcnt++; + } + + buflens[bufcnt++] = req->rq_ctx_init ? GSS_CTX_INIT_MAX_LEN : + gss_cli_payload(req->rq_cli_ctx, txtsize, 0); + + bufsize = lustre_msg_size_v2(bufcnt, buflens); + + if (!req->rq_reqbuf) { + OBD_ALLOC(req->rq_reqbuf, bufsize); + if (!req->rq_reqbuf) + RETURN(-ENOMEM); + + req->rq_reqbuf_len = bufsize; + } else { + LASSERT(req->rq_pool); + LASSERT(req->rq_reqbuf_len >= bufsize); + memset(req->rq_reqbuf, 0, bufsize); + } + + lustre_init_msg_v2(req->rq_reqbuf, bufcnt, buflens, NULL); + req->rq_reqbuf->lm_secflvr = req->rq_sec_flavor; + + req->rq_reqmsg = lustre_msg_buf(req->rq_reqbuf, 1, msgsize); + LASSERT(req->rq_reqmsg); + + /* pack user desc here, later we might leave current user's process */ + if (SEC_FLAVOR_HAS_USER(req->rq_sec_flavor)) + sptlrpc_pack_user_desc(req->rq_reqbuf, 2); + + RETURN(0); +} + +static +int gss_alloc_reqbuf_priv(struct ptlrpc_sec *sec, + struct ptlrpc_request *req, + int msgsize) +{ + struct sec_flavor_config *conf; + int ibuflens[3], ibufcnt; + int buflens[3]; + int clearsize, wiresize; + ENTRY; + + LASSERT(req->rq_clrbuf == NULL); + LASSERT(req->rq_clrbuf_len == 0); + + /* Inner (clear) buffers + * - lustre message + * - user descriptor + * - bulk checksum + */ + ibufcnt = 1; + ibuflens[0] = msgsize; + + if (SEC_FLAVOR_HAS_USER(req->rq_sec_flavor)) + ibuflens[ibufcnt++] = sptlrpc_user_desc_size(); + if (SEC_FLAVOR_HAS_BULK(req->rq_sec_flavor)) { + conf = &req->rq_import->imp_obd->u.cli.cl_sec_conf; + ibuflens[ibufcnt++] = bulk_sec_desc_size(conf->sfc_bulk_csum, 1, + req->rq_bulk_read); + } + clearsize = lustre_msg_size_v2(ibufcnt, ibuflens); + /* to allow append padding during encryption */ + clearsize += GSS_MAX_CIPHER_BLOCK; + + /* Wrapper (wire) buffers + * - gss header + * - signature of gss header + * - cipher text + */ + buflens[0] = PTLRPC_GSS_HEADER_SIZE; + buflens[1] = gss_cli_payload(req->rq_cli_ctx, buflens[0], 0); + buflens[2] = gss_cli_payload(req->rq_cli_ctx, clearsize, 1); + wiresize = lustre_msg_size_v2(3, buflens); + + if (req->rq_pool) { + /* rq_reqbuf is preallocated */ + LASSERT(req->rq_reqbuf); + LASSERT(req->rq_reqbuf_len >= wiresize); + + memset(req->rq_reqbuf, 0, req->rq_reqbuf_len); + + /* if the pre-allocated buffer is big enough, we just pack + * both clear buf & request buf in it, to avoid more alloc. + */ + if (clearsize + wiresize <= req->rq_reqbuf_len) { + req->rq_clrbuf = + (void *) (((char *) req->rq_reqbuf) + wiresize); + } else { + CWARN("pre-allocated buf size %d is not enough for " + "both clear (%d) and cipher (%d) text, proceed " + "with extra allocation\n", req->rq_reqbuf_len, + clearsize, wiresize); + } + } + + if (!req->rq_clrbuf) { + OBD_ALLOC(req->rq_clrbuf, clearsize); + if (!req->rq_clrbuf) + RETURN(-ENOMEM); + } + req->rq_clrbuf_len = clearsize; + + lustre_init_msg_v2(req->rq_clrbuf, ibufcnt, ibuflens, NULL); + req->rq_reqmsg = lustre_msg_buf(req->rq_clrbuf, 0, msgsize); + + if (SEC_FLAVOR_HAS_USER(req->rq_sec_flavor)) + sptlrpc_pack_user_desc(req->rq_clrbuf, 1); + + RETURN(0); +} + +static +int gss_alloc_reqbuf(struct ptlrpc_sec *sec, + struct ptlrpc_request *req, + int msgsize) +{ + LASSERT(!SEC_FLAVOR_HAS_BULK(req->rq_sec_flavor) || + (req->rq_bulk_read || req->rq_bulk_write)); + + switch (SEC_FLAVOR_SVC(req->rq_sec_flavor)) { + case SPTLRPC_SVC_NONE: + case SPTLRPC_SVC_AUTH: + return gss_alloc_reqbuf_auth(sec, req, msgsize); + case SPTLRPC_SVC_PRIV: + return gss_alloc_reqbuf_priv(sec, req, msgsize); + default: + LBUG(); + } + return 0; +} + +static +void gss_free_reqbuf(struct ptlrpc_sec *sec, + struct ptlrpc_request *req) +{ + int privacy; + ENTRY; + + LASSERT(!req->rq_pool || req->rq_reqbuf); + privacy = SEC_FLAVOR_SVC(req->rq_sec_flavor) == SPTLRPC_SVC_PRIV; + + if (!req->rq_clrbuf) + goto release_reqbuf; + + /* release clear buf*/ + LASSERT(privacy); + LASSERT(req->rq_clrbuf_len); + + if (req->rq_pool && + req->rq_clrbuf >= req->rq_reqbuf && + (char *) req->rq_clrbuf < + (char *) req->rq_reqbuf + req->rq_reqbuf_len) + goto release_reqbuf; + + OBD_FREE(req->rq_clrbuf, req->rq_clrbuf_len); + req->rq_clrbuf = NULL; + req->rq_clrbuf_len = 0; + +release_reqbuf: + if (!req->rq_pool && req->rq_reqbuf) { + OBD_FREE(req->rq_reqbuf, req->rq_reqbuf_len); + req->rq_reqbuf = NULL; + req->rq_reqbuf_len = 0; + } + + EXIT; +} + +static +int gss_alloc_repbuf(struct ptlrpc_sec *sec, + struct ptlrpc_request *req, + int msgsize) +{ + struct sec_flavor_config *conf; + int privacy = (SEC_FLAVOR_SVC(req->rq_sec_flavor) == SPTLRPC_SVC_PRIV); + int bufsize, txtsize; + int buflens[4], bufcnt; + ENTRY; + + LASSERT(!SEC_FLAVOR_HAS_BULK(req->rq_sec_flavor) || + (req->rq_bulk_read || req->rq_bulk_write)); + + if (privacy) { + bufcnt = 1; + buflens[0] = msgsize; + if (SEC_FLAVOR_HAS_BULK(req->rq_sec_flavor)) { + conf = &req->rq_import->imp_obd->u.cli.cl_sec_conf; + buflens[bufcnt++] = bulk_sec_desc_size( + conf->sfc_bulk_csum, 0, + req->rq_bulk_read); + } + txtsize = lustre_msg_size_v2(bufcnt, buflens); + txtsize += GSS_MAX_CIPHER_BLOCK; + + bufcnt = 3; + buflens[0] = PTLRPC_GSS_HEADER_SIZE; + buflens[1] = gss_cli_payload(req->rq_cli_ctx, buflens[0], 0); + buflens[2] = gss_cli_payload(req->rq_cli_ctx, txtsize, 1); + } else { + bufcnt = 2; + buflens[0] = PTLRPC_GSS_HEADER_SIZE; + buflens[1] = msgsize; + txtsize = buflens[0] + buflens[1]; + + if (SEC_FLAVOR_HAS_BULK(req->rq_sec_flavor)) { + conf = &req->rq_import->imp_obd->u.cli.cl_sec_conf; + buflens[bufcnt] = bulk_sec_desc_size( + conf->sfc_bulk_csum, 0, + req->rq_bulk_read); + txtsize += buflens[bufcnt]; + bufcnt++; + } + buflens[bufcnt++] = req->rq_ctx_init ? GSS_CTX_INIT_MAX_LEN : + gss_cli_payload(req->rq_cli_ctx, txtsize, 0); + } + + bufsize = lustre_msg_size_v2(bufcnt, buflens); + + OBD_ALLOC(req->rq_repbuf, bufsize); + if (!req->rq_repbuf) + return -ENOMEM; + + req->rq_repbuf_len = bufsize; + return 0; +} + +static +void gss_free_repbuf(struct ptlrpc_sec *sec, + struct ptlrpc_request *req) +{ + OBD_FREE(req->rq_repbuf, req->rq_repbuf_len); + req->rq_repbuf = NULL; + req->rq_repbuf_len = 0; +} + +static +int gss_sec_install_rctx(struct obd_import *imp, + struct ptlrpc_sec *sec, + struct ptlrpc_cli_ctx *ctx) +{ + struct gss_sec *gsec; + struct gss_cli_ctx *gctx; + int rc; + + gsec = container_of(sec, struct gss_sec, gs_base); + gctx = container_of(ctx, struct gss_cli_ctx, gc_base); + + rc = gss_install_rvs_svc_ctx(imp, gsec, gctx); + return rc; +} + +static struct ptlrpc_sec_cops gss_sec_cops = { + .create_sec = gss_sec_create, + .destroy_sec = gss_sec_destroy, + .create_ctx = gss_sec_create_ctx, + .destroy_ctx = gss_sec_destroy_ctx, + .install_rctx = gss_sec_install_rctx, + .alloc_reqbuf = gss_alloc_reqbuf, + .free_reqbuf = gss_free_reqbuf, + .alloc_repbuf = gss_alloc_repbuf, + .free_repbuf = gss_free_repbuf, +}; + +/******************************************** + * server side API * + ********************************************/ + +static inline +int gss_svc_reqctx_is_special(struct gss_svc_reqctx *grctx) +{ + LASSERT(grctx); + return (grctx->src_init || grctx->src_init_continue || + grctx->src_err_notify); +} + +static +void gss_svc_reqctx_free(struct gss_svc_reqctx *grctx) +{ + if (grctx->src_ctx) + gss_svc_upcall_put_ctx(grctx->src_ctx); + + sptlrpc_policy_put(grctx->src_base.sc_policy); + OBD_FREE(grctx, sizeof(*grctx)); +} + +static inline +void gss_svc_reqctx_addref(struct gss_svc_reqctx *grctx) +{ + LASSERT(atomic_read(&grctx->src_base.sc_refcount) > 0); + atomic_inc(&grctx->src_base.sc_refcount); +} + +static inline +void gss_svc_reqctx_decref(struct gss_svc_reqctx *grctx) +{ + LASSERT(atomic_read(&grctx->src_base.sc_refcount) > 0); + + if (atomic_dec_and_test(&grctx->src_base.sc_refcount)) + gss_svc_reqctx_free(grctx); +} + +static +int gss_svc_sign(struct ptlrpc_request *req, + struct ptlrpc_reply_state *rs, + struct gss_svc_reqctx *grctx) +{ + int rc; + ENTRY; + + LASSERT(rs->rs_msg == lustre_msg_buf(rs->rs_repbuf, 1, 0)); + + /* embedded lustre_msg might have been shrinked */ + if (req->rq_replen != rs->rs_repbuf->lm_buflens[1]) + lustre_shrink_msg(rs->rs_repbuf, 1, req->rq_replen, 1); + + rc = gss_sign_msg(rs->rs_repbuf, grctx->src_ctx->gsc_mechctx, + PTLRPC_GSS_PROC_DATA, grctx->src_wirectx.gw_seq, + NULL); + if (rc < 0) + RETURN(rc); + + rs->rs_repdata_len = rc; + RETURN(0); +} + +int gss_pack_err_notify(struct ptlrpc_request *req, __u32 major, __u32 minor) +{ + struct gss_svc_reqctx *grctx = gss_svc_ctx2reqctx(req->rq_svc_ctx); + struct ptlrpc_reply_state *rs; + struct gss_err_header *ghdr; + int replen = sizeof(struct ptlrpc_body); + int rc; + ENTRY; + + //OBD_FAIL_RETURN(OBD_FAIL_SVCGSS_ERR_NOTIFY|OBD_FAIL_ONCE, -EINVAL); + + grctx->src_err_notify = 1; + grctx->src_reserve_len = 0; + + rc = lustre_pack_reply_v2(req, 1, &replen, NULL); + if (rc) { + CERROR("could not pack reply, err %d\n", rc); + RETURN(rc); + } + + /* gss hdr */ + rs = req->rq_reply_state; + LASSERT(rs->rs_repbuf->lm_buflens[1] >= sizeof(*ghdr)); + ghdr = lustre_msg_buf(rs->rs_repbuf, 0, 0); + ghdr->gh_version = PTLRPC_GSS_VERSION; + ghdr->gh_flags = 0; + ghdr->gh_proc = PTLRPC_GSS_PROC_ERR; + ghdr->gh_major = major; + ghdr->gh_minor = minor; + ghdr->gh_handle.len = 0; /* fake context handle */ + + rs->rs_repdata_len = lustre_msg_size_v2(rs->rs_repbuf->lm_bufcount, + rs->rs_repbuf->lm_buflens); + + CDEBUG(D_SEC, "prepare gss error notify(0x%x/0x%x) to %s\n", + major, minor, libcfs_nid2str(req->rq_peer.nid)); + RETURN(0); +} + +static +int gss_svc_handle_init(struct ptlrpc_request *req, + struct gss_wire_ctx *gw) +{ + struct gss_svc_reqctx *grctx = gss_svc_ctx2reqctx(req->rq_svc_ctx); + struct lustre_msg *reqbuf = req->rq_reqbuf; + struct obd_uuid *uuid; + struct obd_device *target; + rawobj_t uuid_obj, rvs_hdl, in_token; + __u32 lustre_svc; + __u32 *secdata, seclen; + int rc; + ENTRY; + + CDEBUG(D_SEC, "processing gss init(%d) request from %s\n", gw->gw_proc, + libcfs_nid2str(req->rq_peer.nid)); + + if (gw->gw_proc == PTLRPC_GSS_PROC_INIT && gw->gw_handle.len != 0) { + CERROR("proc %u: invalid handle length %u\n", + gw->gw_proc, gw->gw_handle.len); + RETURN(SECSVC_DROP); + } + + if (reqbuf->lm_bufcount < 3 || reqbuf->lm_bufcount > 4){ + CERROR("Invalid bufcount %d\n", reqbuf->lm_bufcount); + RETURN(SECSVC_DROP); + } + + /* ctx initiate payload is in last segment */ + secdata = lustre_msg_buf(reqbuf, reqbuf->lm_bufcount - 1, 0); + seclen = reqbuf->lm_buflens[reqbuf->lm_bufcount - 1]; + + if (seclen < 4 + 4) { + CERROR("sec size %d too small\n", seclen); + RETURN(SECSVC_DROP); + } + + /* lustre svc type */ + lustre_svc = le32_to_cpu(*secdata++); + seclen -= 4; + + /* extract target uuid, note this code is somewhat fragile + * because touched internal structure of obd_uuid + */ + if (rawobj_extract(&uuid_obj, &secdata, &seclen)) { + CERROR("failed to extract target uuid\n"); + RETURN(SECSVC_DROP); + } + uuid_obj.data[uuid_obj.len - 1] = '\0'; + + uuid = (struct obd_uuid *) uuid_obj.data; + target = class_uuid2obd(uuid); + if (!target || target->obd_stopping || !target->obd_set_up) { + CERROR("target '%s' is not available for context init (%s)", + uuid->uuid, target == NULL ? "no target" : + (target->obd_stopping ? "stopping" : "not set up")); + RETURN(SECSVC_DROP); + } + + /* extract reverse handle */ + if (rawobj_extract(&rvs_hdl, &secdata, &seclen)) { + CERROR("failed extract reverse handle\n"); + RETURN(SECSVC_DROP); + } + + /* extract token */ + if (rawobj_extract(&in_token, &secdata, &seclen)) { + CERROR("can't extract token\n"); + RETURN(SECSVC_DROP); + } + + rc = gss_svc_upcall_handle_init(req, grctx, gw, target, lustre_svc, + &rvs_hdl, &in_token); + if (rc != SECSVC_OK) + RETURN(rc); + + if (SEC_FLAVOR_HAS_USER(req->rq_sec_flavor)) { + if (reqbuf->lm_bufcount < 4) { + CERROR("missing user descriptor\n"); + RETURN(SECSVC_DROP); + } + if (sptlrpc_unpack_user_desc(reqbuf, 2)) { + CERROR("Mal-formed user descriptor\n"); + RETURN(SECSVC_DROP); + } + req->rq_user_desc = lustre_msg_buf(reqbuf, 2, 0); + } + + req->rq_reqmsg = lustre_msg_buf(reqbuf, 1, 0); + req->rq_reqlen = lustre_msg_buflen(reqbuf, 1); + + RETURN(rc); +} + +/* + * last segment must be the gss signature. + */ +static +int gss_svc_verify_request(struct ptlrpc_request *req, + struct gss_svc_ctx *gctx, + struct gss_wire_ctx *gw, + __u32 *major) +{ + struct lustre_msg *msg = req->rq_reqbuf; + int offset = 2; + ENTRY; + + *major = GSS_S_COMPLETE; + + if (msg->lm_bufcount < 3) { + CERROR("Too few segments (%u) in request\n", msg->lm_bufcount); + RETURN(-EINVAL); + } + + if (gss_check_seq_num(&gctx->gsc_seqdata, gw->gw_seq, 0)) { + CERROR("phase 1: discard replayed req: seq %u\n", gw->gw_seq); + *major = GSS_S_DUPLICATE_TOKEN; + RETURN(-EACCES); + } + + *major = gss_verify_msg(msg, gctx->gsc_mechctx); + if (*major != GSS_S_COMPLETE) + RETURN(-EACCES); + + if (gss_check_seq_num(&gctx->gsc_seqdata, gw->gw_seq, 1)) { + CERROR("phase 2: discard replayed req: seq %u\n", gw->gw_seq); + *major = GSS_S_DUPLICATE_TOKEN; + RETURN(-EACCES); + } + + /* user descriptor */ + if (SEC_FLAVOR_HAS_USER(req->rq_sec_flavor)) { + if (msg->lm_bufcount < (offset + 1 + 1)) { + CERROR("no user desc included\n"); + RETURN(-EINVAL); + } + + if (sptlrpc_unpack_user_desc(msg, offset)) { + CERROR("Mal-formed user descriptor\n"); + RETURN(-EINVAL); + } + + req->rq_user_desc = lustre_msg_buf(msg, offset, 0); + offset++; + } + + /* check bulk cksum data */ + if (SEC_FLAVOR_HAS_BULK(req->rq_sec_flavor)) { + if (msg->lm_bufcount < (offset + 1 + 1)) { + CERROR("no bulk checksum included\n"); + RETURN(-EINVAL); + } + + if (bulk_sec_desc_unpack(msg, offset)) + RETURN(-EINVAL); + } + + req->rq_reqmsg = lustre_msg_buf(msg, 1, 0); + req->rq_reqlen = msg->lm_buflens[1]; + RETURN(0); +} + +static +int gss_svc_unseal_request(struct ptlrpc_request *req, + struct gss_svc_ctx *gctx, + struct gss_wire_ctx *gw, + __u32 *major) +{ + struct lustre_msg *msg = req->rq_reqbuf; + int msglen, offset = 1; + ENTRY; + + if (gss_check_seq_num(&gctx->gsc_seqdata, gw->gw_seq, 0)) { + CERROR("phase 1: discard replayed req: seq %u\n", gw->gw_seq); + *major = GSS_S_DUPLICATE_TOKEN; + RETURN(-EACCES); + } + + *major = gss_unseal_msg(gctx->gsc_mechctx, msg, + &msglen, req->rq_reqdata_len); + if (*major != GSS_S_COMPLETE) + RETURN(-EACCES); + + if (gss_check_seq_num(&gctx->gsc_seqdata, gw->gw_seq, 1)) { + CERROR("phase 2: discard replayed req: seq %u\n", gw->gw_seq); + *major = GSS_S_DUPLICATE_TOKEN; + RETURN(-EACCES); + } + + if (lustre_unpack_msg(msg, msglen)) { + CERROR("Failed to unpack after decryption\n"); + RETURN(-EINVAL); + } + req->rq_reqdata_len = msglen; + + if (msg->lm_bufcount < 1) { + CERROR("Invalid buffer: is empty\n"); + RETURN(-EINVAL); + } + + if (SEC_FLAVOR_HAS_USER(req->rq_sec_flavor)) { + if (msg->lm_bufcount < offset + 1) { + CERROR("no user descriptor included\n"); + RETURN(-EINVAL); + } + + if (sptlrpc_unpack_user_desc(msg, offset)) { + CERROR("Mal-formed user descriptor\n"); + RETURN(-EINVAL); + } + + req->rq_user_desc = lustre_msg_buf(msg, offset, 0); + offset++; + } + + if (SEC_FLAVOR_HAS_BULK(req->rq_sec_flavor)) { + if (msg->lm_bufcount < offset + 1) { + CERROR("no bulk checksum included\n"); + RETURN(-EINVAL); + } + + if (bulk_sec_desc_unpack(msg, offset)) + RETURN(-EINVAL); + } + + req->rq_reqmsg = lustre_msg_buf(req->rq_reqbuf, 0, 0); + req->rq_reqlen = req->rq_reqbuf->lm_buflens[0]; + RETURN(0); +} + +static +int gss_svc_handle_data(struct ptlrpc_request *req, + struct gss_wire_ctx *gw) +{ + struct gss_svc_reqctx *grctx = gss_svc_ctx2reqctx(req->rq_svc_ctx); + __u32 major = 0; + int rc = 0; + ENTRY; + + grctx->src_ctx = gss_svc_upcall_get_ctx(req, gw); + if (!grctx->src_ctx) { + major = GSS_S_NO_CONTEXT; + goto error; + } + + switch (gw->gw_svc) { + case PTLRPC_GSS_SVC_INTEGRITY: + rc = gss_svc_verify_request(req, grctx->src_ctx, gw, &major); + break; + case PTLRPC_GSS_SVC_PRIVACY: + rc = gss_svc_unseal_request(req, grctx->src_ctx, gw, &major); + break; + default: + CERROR("unsupported gss service %d\n", gw->gw_svc); + rc = -EINVAL; + } + + if (rc != 0) + goto error; + + RETURN(SECSVC_OK); + +error: + CERROR("svc %u failed: major 0x%08x: ctx %p(%u->%s)\n", + gw->gw_svc, major, grctx->src_ctx, grctx->src_ctx->gsc_uid, + libcfs_nid2str(req->rq_peer.nid)); + /* + * we only notify client in case of NO_CONTEXT/BAD_SIG, which + * might happen after server reboot, to allow recovery. + */ + if ((major == GSS_S_NO_CONTEXT || major == GSS_S_BAD_SIG) && + gss_pack_err_notify(req, major, 0) == 0) + RETURN(SECSVC_COMPLETE); + + RETURN(SECSVC_DROP); +} + +static +int gss_svc_handle_destroy(struct ptlrpc_request *req, + struct gss_wire_ctx *gw) +{ + struct gss_svc_reqctx *grctx = gss_svc_ctx2reqctx(req->rq_svc_ctx); + int replen = sizeof(struct ptlrpc_body); + __u32 major; + ENTRY; + + grctx->src_ctx = gss_svc_upcall_get_ctx(req, gw); + if (!grctx->src_ctx) { + CWARN("invalid gss context handle for destroy.\n"); + RETURN(SECSVC_DROP); + } + + if (gw->gw_svc != PTLRPC_GSS_SVC_INTEGRITY) { + CERROR("svc %u is not supported in destroy.\n", gw->gw_svc); + RETURN(SECSVC_DROP); + } + + if (gss_svc_verify_request(req, grctx->src_ctx, gw, &major)) + RETURN(SECSVC_DROP); + + if (lustre_pack_reply_v2(req, 1, &replen, NULL)) + RETURN(SECSVC_DROP); + + CWARN("gss svc destroy ctx %p(%u->%s)\n", grctx->src_ctx, + grctx->src_ctx->gsc_uid, libcfs_nid2str(req->rq_peer.nid)); + + gss_svc_upcall_destroy_ctx(grctx->src_ctx); + + if (SEC_FLAVOR_HAS_USER(req->rq_sec_flavor)) { + if (req->rq_reqbuf->lm_bufcount < 4) { + CERROR("missing user descriptor, ignore it\n"); + RETURN(SECSVC_OK); + } + if (sptlrpc_unpack_user_desc(req->rq_reqbuf, 2)) { + CERROR("Mal-formed user descriptor, ignore it\n"); + RETURN(SECSVC_OK); + } + req->rq_user_desc = lustre_msg_buf(req->rq_reqbuf, 2, 0); + } + + RETURN(SECSVC_OK); +} + +static +int gss_svc_accept(struct ptlrpc_request *req) +{ + struct gss_header *ghdr; + struct gss_svc_reqctx *grctx; + struct gss_wire_ctx *gw; + int rc; + ENTRY; + + LASSERT(req->rq_reqbuf); + LASSERT(req->rq_svc_ctx == NULL); + + if (req->rq_reqbuf->lm_bufcount < 2) { + CERROR("buf count only %d\n", req->rq_reqbuf->lm_bufcount); + RETURN(SECSVC_DROP); + } + + ghdr = gss_swab_header(req->rq_reqbuf, 0); + if (ghdr == NULL) { + CERROR("can't decode gss header\n"); + RETURN(SECSVC_DROP); + } + + /* sanity checks */ + if (ghdr->gh_version != PTLRPC_GSS_VERSION) { + CERROR("gss version %u, expect %u\n", ghdr->gh_version, + PTLRPC_GSS_VERSION); + RETURN(SECSVC_DROP); + } + + /* alloc grctx data */ + OBD_ALLOC(grctx, sizeof(*grctx)); + if (!grctx) { + CERROR("fail to alloc svc reqctx\n"); + RETURN(SECSVC_DROP); + } + grctx->src_base.sc_policy = sptlrpc_policy_get(&gss_policy); + atomic_set(&grctx->src_base.sc_refcount, 1); + req->rq_svc_ctx = &grctx->src_base; + gw = &grctx->src_wirectx; + + /* save wire context */ + gw->gw_proc = ghdr->gh_proc; + gw->gw_seq = ghdr->gh_seq; + gw->gw_svc = ghdr->gh_svc; + rawobj_from_netobj(&gw->gw_handle, &ghdr->gh_handle); + + /* keep original wire header which subject to checksum verification */ + if (lustre_msg_swabbed(req->rq_reqbuf)) + gss_header_swabber(ghdr); + + switch(ghdr->gh_proc) { + case PTLRPC_GSS_PROC_INIT: + case PTLRPC_GSS_PROC_CONTINUE_INIT: + rc = gss_svc_handle_init(req, gw); + break; + case PTLRPC_GSS_PROC_DATA: + rc = gss_svc_handle_data(req, gw); + break; + case PTLRPC_GSS_PROC_DESTROY: + rc = gss_svc_handle_destroy(req, gw); + break; + default: + CERROR("unknown proc %u\n", gw->gw_proc); + rc = SECSVC_DROP; + break; + } + + switch (rc) { + case SECSVC_OK: + LASSERT (grctx->src_ctx); + + req->rq_auth_gss = 1; + req->rq_auth_remote = grctx->src_ctx->gsc_remote; + req->rq_auth_usr_mds = grctx->src_ctx->gsc_usr_mds; + req->rq_auth_usr_root = grctx->src_ctx->gsc_usr_root; + req->rq_auth_uid = grctx->src_ctx->gsc_uid; + req->rq_auth_mapped_uid = grctx->src_ctx->gsc_mapped_uid; + break; + case SECSVC_COMPLETE: + break; + case SECSVC_DROP: + gss_svc_reqctx_free(grctx); + req->rq_svc_ctx = NULL; + break; + } + + RETURN(rc); +} + +static inline +int gss_svc_payload(struct gss_svc_reqctx *grctx, int msgsize, int privacy) +{ + if (gss_svc_reqctx_is_special(grctx)) + return grctx->src_reserve_len; + + return gss_estimate_payload(NULL, msgsize, privacy); +} + +static +int gss_svc_alloc_rs(struct ptlrpc_request *req, int msglen) +{ + struct gss_svc_reqctx *grctx; + struct ptlrpc_reply_state *rs; + struct ptlrpc_bulk_sec_desc *bsd; + int privacy; + int ibuflens[2], ibufcnt = 0; + int buflens[4], bufcnt; + int txtsize, wmsg_size, rs_size; + ENTRY; + + LASSERT(msglen % 8 == 0); + + if (SEC_FLAVOR_HAS_BULK(req->rq_sec_flavor) && + !req->rq_bulk_read && !req->rq_bulk_write) { + CERROR("client request bulk sec on non-bulk rpc\n"); + RETURN(-EPROTO); + } + + grctx = gss_svc_ctx2reqctx(req->rq_svc_ctx); + if (gss_svc_reqctx_is_special(grctx)) + privacy = 0; + else + privacy = (SEC_FLAVOR_SVC(req->rq_sec_flavor) == + SPTLRPC_SVC_PRIV); + + if (privacy) { + /* Inner buffer */ + ibufcnt = 1; + ibuflens[0] = msglen; + + if (SEC_FLAVOR_HAS_BULK(req->rq_sec_flavor)) { + LASSERT(req->rq_reqbuf->lm_bufcount >= 2); + bsd = lustre_msg_buf(req->rq_reqbuf, + req->rq_reqbuf->lm_bufcount - 1, + sizeof(*bsd)); + + ibuflens[ibufcnt++] = bulk_sec_desc_size( + bsd->bsd_csum_alg, 0, + req->rq_bulk_read); + } + + txtsize = lustre_msg_size_v2(ibufcnt, ibuflens); + txtsize += GSS_MAX_CIPHER_BLOCK; + + /* wrapper buffer */ + bufcnt = 3; + buflens[0] = PTLRPC_GSS_HEADER_SIZE; + buflens[1] = gss_svc_payload(grctx, buflens[0], 0); + buflens[2] = gss_svc_payload(grctx, txtsize, 1); + } else { + bufcnt = 2; + buflens[0] = PTLRPC_GSS_HEADER_SIZE; + buflens[1] = msglen; + txtsize = buflens[0] + buflens[1]; + + if (SEC_FLAVOR_HAS_BULK(req->rq_sec_flavor)) { + LASSERT(req->rq_reqbuf->lm_bufcount >= 4); + bsd = lustre_msg_buf(req->rq_reqbuf, + req->rq_reqbuf->lm_bufcount - 2, + sizeof(*bsd)); + + buflens[bufcnt] = bulk_sec_desc_size( + bsd->bsd_csum_alg, 0, + req->rq_bulk_read); + txtsize += buflens[bufcnt]; + bufcnt++; + } + buflens[bufcnt++] = gss_svc_payload(grctx, txtsize, 0); + } + + wmsg_size = lustre_msg_size_v2(bufcnt, buflens); + + rs_size = sizeof(*rs) + wmsg_size; + rs = req->rq_reply_state; + + if (rs) { + /* pre-allocated */ + LASSERT(rs->rs_size >= rs_size); + } else { + OBD_ALLOC(rs, rs_size); + if (rs == NULL) + RETURN(-ENOMEM); + + rs->rs_size = rs_size; + } + + rs->rs_repbuf = (struct lustre_msg *) (rs + 1); + rs->rs_repbuf_len = wmsg_size; + + if (privacy) { + lustre_init_msg_v2(rs->rs_repbuf, ibufcnt, ibuflens, NULL); + rs->rs_msg = lustre_msg_buf(rs->rs_repbuf, 0, msglen); + } else { + lustre_init_msg_v2(rs->rs_repbuf, bufcnt, buflens, NULL); + rs->rs_repbuf->lm_secflvr = req->rq_sec_flavor; + + rs->rs_msg = (struct lustre_msg *) + lustre_msg_buf(rs->rs_repbuf, 1, 0); + } + + gss_svc_reqctx_addref(grctx); + rs->rs_svc_ctx = req->rq_svc_ctx; + + LASSERT(rs->rs_msg); + req->rq_reply_state = rs; + RETURN(0); +} + +static +int gss_svc_seal(struct ptlrpc_request *req, + struct ptlrpc_reply_state *rs, + struct gss_svc_reqctx *grctx) +{ + struct gss_svc_ctx *gctx = grctx->src_ctx; + rawobj_t msgobj, cipher_obj, micobj; + struct gss_header *ghdr; + __u8 *cipher_buf; + int cipher_buflen, buflens[3]; + int msglen, rc; + __u32 major; + ENTRY; + + /* embedded lustre_msg might have been shrinked */ + if (req->rq_replen != rs->rs_repbuf->lm_buflens[0]) + lustre_shrink_msg(rs->rs_repbuf, 0, req->rq_replen, 1); + + /* clear data length */ + msglen = lustre_msg_size_v2(rs->rs_repbuf->lm_bufcount, + rs->rs_repbuf->lm_buflens); + + /* clear text */ + msgobj.len = msglen; + msgobj.data = (__u8 *) rs->rs_repbuf; + + /* allocate temporary cipher buffer */ + cipher_buflen = gss_estimate_payload(gctx->gsc_mechctx, msglen, 1); + OBD_ALLOC(cipher_buf, cipher_buflen); + if (!cipher_buf) + RETURN(-ENOMEM); + + cipher_obj.len = cipher_buflen; + cipher_obj.data = cipher_buf; + + major = lgss_wrap(gctx->gsc_mechctx, &msgobj, rs->rs_repbuf_len, + &cipher_obj); + if (major != GSS_S_COMPLETE) { + CERROR("priv: wrap message error: %08x\n", major); + GOTO(out_free, rc = -EPERM); + } + LASSERT(cipher_obj.len <= cipher_buflen); + + /* now the real wire data */ + buflens[0] = PTLRPC_GSS_HEADER_SIZE; + buflens[1] = gss_estimate_payload(gctx->gsc_mechctx, buflens[0], 0); + buflens[2] = cipher_obj.len; + + LASSERT(lustre_msg_size_v2(3, buflens) <= rs->rs_repbuf_len); + lustre_init_msg_v2(rs->rs_repbuf, 3, buflens, NULL); + rs->rs_repbuf->lm_secflvr = req->rq_sec_flavor; + + /* gss header */ + ghdr = lustre_msg_buf(rs->rs_repbuf, 0, 0); + ghdr->gh_version = PTLRPC_GSS_VERSION; + ghdr->gh_flags = 0; + ghdr->gh_proc = PTLRPC_GSS_PROC_DATA; + ghdr->gh_seq = grctx->src_wirectx.gw_seq; + ghdr->gh_svc = PTLRPC_GSS_SVC_PRIVACY; + ghdr->gh_handle.len = 0; + + /* header signature */ + msgobj.len = rs->rs_repbuf->lm_buflens[0]; + msgobj.data = lustre_msg_buf(rs->rs_repbuf, 0, 0); + micobj.len = rs->rs_repbuf->lm_buflens[1]; + micobj.data = lustre_msg_buf(rs->rs_repbuf, 1, 0); + + major = lgss_get_mic(gctx->gsc_mechctx, 1, &msgobj, &micobj); + if (major != GSS_S_COMPLETE) { + CERROR("priv: sign message error: %08x\n", major); + GOTO(out_free, rc = -EPERM); + } + lustre_shrink_msg(rs->rs_repbuf, 1, micobj.len, 0); + + /* cipher token */ + memcpy(lustre_msg_buf(rs->rs_repbuf, 2, 0), + cipher_obj.data, cipher_obj.len); + + rs->rs_repdata_len = lustre_shrink_msg(rs->rs_repbuf, 2, + cipher_obj.len, 0); + + /* to catch upper layer's further access */ + rs->rs_msg = NULL; + req->rq_repmsg = NULL; + req->rq_replen = 0; + + rc = 0; +out_free: + OBD_FREE(cipher_buf, cipher_buflen); + RETURN(rc); +} + +int gss_svc_authorize(struct ptlrpc_request *req) +{ + struct ptlrpc_reply_state *rs = req->rq_reply_state; + struct gss_svc_reqctx *grctx = gss_svc_ctx2reqctx(req->rq_svc_ctx); + struct gss_wire_ctx *gw; + int rc; + ENTRY; + + if (gss_svc_reqctx_is_special(grctx)) + RETURN(0); + + gw = &grctx->src_wirectx; + if (gw->gw_proc != PTLRPC_GSS_PROC_DATA && + gw->gw_proc != PTLRPC_GSS_PROC_DESTROY) { + CERROR("proc %d not support\n", gw->gw_proc); + RETURN(-EINVAL); + } + + LASSERT(grctx->src_ctx); + + switch (gw->gw_svc) { + case PTLRPC_GSS_SVC_INTEGRITY: + rc = gss_svc_sign(req, rs, grctx); + break; + case PTLRPC_GSS_SVC_PRIVACY: + rc = gss_svc_seal(req, rs, grctx); + break; + default: + CERROR("Unknown service %d\n", gw->gw_svc); + GOTO(out, rc = -EINVAL); + } + rc = 0; + +out: + RETURN(rc); +} + +static +void gss_svc_free_rs(struct ptlrpc_reply_state *rs) +{ + struct gss_svc_reqctx *grctx; + + LASSERT(rs->rs_svc_ctx); + grctx = container_of(rs->rs_svc_ctx, struct gss_svc_reqctx, src_base); + + gss_svc_reqctx_decref(grctx); + rs->rs_svc_ctx = NULL; + + if (!rs->rs_prealloc) + OBD_FREE(rs, rs->rs_size); +} + +static +void gss_svc_free_ctx(struct ptlrpc_svc_ctx *ctx) +{ + LASSERT(atomic_read(&ctx->sc_refcount) == 0); + gss_svc_reqctx_free(gss_svc_ctx2reqctx(ctx)); +} + +static +int gss_svc_install_rctx(struct obd_import *imp, struct ptlrpc_svc_ctx *ctx) +{ + struct gss_sec *gsec; + + LASSERT(imp->imp_sec); + LASSERT(ctx); + + gsec = container_of(imp->imp_sec, struct gss_sec, gs_base); + return gss_install_rvs_cli_ctx(gsec, ctx); +} + +static struct ptlrpc_sec_sops gss_sec_sops = { + .accept = gss_svc_accept, + .alloc_rs = gss_svc_alloc_rs, + .authorize = gss_svc_authorize, + .free_rs = gss_svc_free_rs, + .free_ctx = gss_svc_free_ctx, + .unwrap_bulk = gss_svc_unwrap_bulk, + .wrap_bulk = gss_svc_wrap_bulk, + .install_rctx = gss_svc_install_rctx, +}; + +static struct ptlrpc_sec_policy gss_policy = { + .sp_owner = THIS_MODULE, + .sp_name = "sec.gss", + .sp_policy = SPTLRPC_POLICY_GSS, + .sp_cops = &gss_sec_cops, + .sp_sops = &gss_sec_sops, +}; + +int __init sptlrpc_gss_init(void) +{ + int rc; + + rc = sptlrpc_register_policy(&gss_policy); + if (rc) + return rc; + + rc = gss_init_lproc(); + if (rc) + goto out_type; + + rc = gss_init_upcall(); + if (rc) + goto out_lproc; + + rc = init_kerberos_module(); + if (rc) + goto out_upcall; + + return 0; +out_upcall: + gss_exit_upcall(); +out_lproc: + gss_exit_lproc(); +out_type: + sptlrpc_unregister_policy(&gss_policy); + return rc; +} + +static void __exit sptlrpc_gss_exit(void) +{ + cleanup_kerberos_module(); + gss_exit_upcall(); + gss_exit_lproc(); + sptlrpc_unregister_policy(&gss_policy); +} + +MODULE_AUTHOR("Cluster File Systems, Inc. "); +MODULE_DESCRIPTION("GSS security policy for Lustre"); +MODULE_LICENSE("GPL"); + +module_init(sptlrpc_gss_init); +module_exit(sptlrpc_gss_exit); diff --git a/lustre/tests/conf-sanity.sh b/lustre/tests/conf-sanity.sh index e903dbe..0f8567f 100644 --- a/lustre/tests/conf-sanity.sh +++ b/lustre/tests/conf-sanity.sh @@ -156,6 +156,7 @@ fi gen_config +init_krb5_env test_0() { setup @@ -857,5 +858,6 @@ run_test 23 "interrupt client during recovery mount delay" umount_client $MOUNT cleanup_nocli +cleanup_krb5_env equals_msg "Done" diff --git a/lustre/tests/krb5_login.sh b/lustre/tests/krb5_login.sh new file mode 100755 index 0000000..e80ae7c2 --- /dev/null +++ b/lustre/tests/krb5_login.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +# +# nothing need for root +# +if [ $UID -eq 0 ]; then + exit 0 +fi + +if [ -z "$KRB5DIR" ]; then + KRB5DIR=/usr/kerberos +fi + +$KRB5DIR/bin/klist -5 -s +invalid=$? + +if [ $invalid -eq 0 ]; then + exit 0 +fi + +echo "***** refresh Kerberos V5 TGT for uid $UID *****" +if [ -z "$GSS_PASS" ]; then + $KRB5DIR/bin/kinit +else + expect < $file + chmod 0666 $file + $CHECKSTAT -p 0666 $file || error "$UID checkstat error" + $RUNAS $CHECKSTAT -p 0666 $file || error "$RUNAS_ID checkstat error" + $RUNAS cat $file > /dev/null || error "$RUNAS_ID cat error" + + # start multiop + $RUNAS multiop $file o_r & + OPPID=$! + # wait multiop finish its open() + sleep 1 + + # cleanup all cred/ctx and check + # metadata check should fail, but file data check should success + # because we always use root credential to OSTs + $RUNAS kdestroy + $RUNAS $LFS flushctx + $RUNAS $CHECKSTAT -p 0666 $file && error "checkstat succeed" + kill -s 10 $OPPID + wait $OPPID || error "read file data failed" + echo "read file data OK" + + # restore and check again + restore_krb5_cred + $RUNAS $CHECKSTAT -p 0666 $file || error "$RUNAS_ID checkstat (2) error" + $CHECKSTAT -p 0666 $file || error "$UID checkstat (2) error" + $RUNAS cat $file > /dev/null || error "$RUNAS_ID cat (2) error" +} +run_test 3 "local cache under DLM lock" + +test_4() { + local file1=$MOUNT/f4_1 + local file2=$MOUNT/f4_2 + + # current access should be ok + $RUNAS touch $file1 || error "can't touch $file1" + [ -f $file1 ] || error "$file1 not found" + + # stop lgssd + send_sigint client lgssd + sleep 5 + check_gss_daemon_facet client lgssd && error "lgssd still running" + + # flush context, and touch + $RUNAS $LFS flushctx + $RUNAS touch $file2 & + TOUCHPID=$! + echo "waiting touch pid $TOUCHPID" + wait $TOUCHPID && error "touch should fail" + + # restart lgssd + do_facet client "$LGSSD -v" + sleep 5 + check_gss_daemon_facet client lgssd + + # touch new should succeed + $RUNAS touch $file2 || error "can't touch $file2" + [ -f $file2 ] || error "$file2 not found" +} +run_test 4 "lgssd dead, operations should wait timeout and fail" + +test_5() { + local file1=$MOUNT/f5_1 + local file2=$MOUNT/f5_2 + local wait_time=120 + + # current access should be ok + $RUNAS touch $file1 || error "can't touch $file1" + [ -f $file1 ] || error "$file1 not found" + + # stop lsvcgssd + send_sigint mds lsvcgssd + sleep 5 + check_gss_daemon_facet mds lsvcgssd && error "lsvcgssd still running" + + # flush context, and touch + $RUNAS $LFS flushctx + $RUNAS touch $file2 & + TOUCHPID=$! + + # wait certain time + echo "waiting $wait_time seconds for touch pid $TOUCHPID" + sleep $wait_time + num=`ps --no-headers -p $TOUCHPID | wc -l` + [ $num -eq 1 ] || error "touch already ended ($num)" + echo "process $TOUCHPID still hanging there... OK" + + # restart lsvcgssd, expect touch suceed + echo "restart lsvcgssd and recovering" + do_facet mds "$LSVCGSSD -v" + sleep 5 + check_gss_daemon_facet mds lsvcgssd + wait $TOUCHPID || error "touch fail" + [ -f $file2 ] || error "$file2 not found" +} +run_test 5 "lsvcgssd dead, operations lead to recovery" + +test_6() { + NPROC=`cat /proc/cpuinfo 2>/dev/null | grep ^processor | wc -l` + [ $NPROC -ne 0 ] || NPROC=2 + + echo "starting dbench $NPROC" + sh rundbench $NPROC & + RUNPID=$! + + for ((n=0;;n++)); do + sleep 2 + num=`ps --no-headers -p $RUNPID | wc -l` + [ $num -ne 0 ] || break + echo "flush ctx ..." + $LFS flushctx + done + wait $RUNPID || error "dbench detect error" +} +run_test 6 "recoverable from losing context" + +check_multiple_gss_daemons() { + local facet=$1 + + for ((i=0;i<10;i++)); do + do_facet $facet "$LSVCGSSD -v &" + done + for ((i=0;i<10;i++)); do + do_facet $facet "$LGSSD -v &" + done + + # wait daemons entering "stable" status + sleep 5 + + numc=`do_facet $facet ps -o cmd -C lgssd | grep lgssd | wc -l` + nums=`do_facet $facet ps -o cmd -C lgssd | grep lgssd | wc -l` + echo "$numc lgssd and $nums lsvcgssd are running" + + if [ $numc -ne 1 -o $nums -ne 1 ]; then + error "lgssd/lsvcgssd not unique" + fi +} + +test_100() { + local facet=mds + + # cleanup everything at first + cleanupall + + echo "bring up gss daemons..." + start_gss_daemons + + echo "check with someone already running..." + check_multiple_gss_daemons $facet + + echo "check with someone run & finished..." + do_facet $facet killall -q -2 lgssd lsvcgssd || true + sleep 5 # wait fully exit + check_multiple_gss_daemons $facet + + echo "check refresh..." + do_facet $facet killall -q -2 lgssd lsvcgssd || true + sleep 5 # wait fully exit + do_facet $facet ipcrm -S 0x3b92d473 + do_facet $facet ipcrm -S 0x3a92d473 + check_multiple_gss_daemons $facet + + stop_gss_daemons +} +run_test 100 "start more multiple gss daemons" + +TMPDIR=$OLDTMPDIR +TMP=$OLDTMP +HOME=$OLDHOME + +log "cleanup: ======================================================" +if [ "`mount | grep ^$NAME`" ]; then + rm -rf $DIR/[Rdfs][1-9]* +fi + +cleanupall -f || error "cleanup failed" + + +echo '=========================== finished ===============================' +[ -f "$SANITYLOG" ] && cat $SANITYLOG && exit 1 || true diff --git a/lustre/tests/sanity.sh b/lustre/tests/sanity.sh index 7c9a609..b320fc4 100644 --- a/lustre/tests/sanity.sh +++ b/lustre/tests/sanity.sh @@ -79,6 +79,11 @@ LUSTRE=${LUSTRE:-`dirname $0`/..} init_test_env $@ . ${CONFIG:=$LUSTRE/tests/cfg/local.sh} +if [ ! -z "$USING_KRB5" ]; then + $RUNAS krb5_login.sh || exit 1 + $RUNAS -u $(($RUNAS_ID + 1)) krb5_login.sh || exit 1 +fi + cleanup() { echo -n "cln.." cleanupall ${FORCE} $* || { echo "FAILed to clean up"; exit 20; } @@ -2626,6 +2631,8 @@ run_test 68 "support swapping to Lustre ========================" test_69() { [ -z "`lsmod|grep obdfilter`" ] && echo "skipping $TESTNAME (remote OST)" && return + [ ! -z "$USING_KRB5" ] && + echo "skipping $TESTNAME (gss with bulk security will triger oops. re-enable this after b10091 get fixed)" && return f="$DIR/$tfile" touch $f @@ -3028,6 +3035,7 @@ test_103 () { [ "$UID" != 0 ] && echo "skipping $TESTNAME (must run as root)" && return [ -z "$(grep acl $LPROC/mdc/*-mdc-*/connect_flags)" ] && echo "skipping $TESTNAME (must have acl enabled)" && return [ -z "$(which setfacl 2>/dev/null)" ] && echo "skipping $TESTNAME (could not find setfacl)" && return + [ ! -z "$USING_KRB5" ] && echo "skipping $TESTNAME (could not run under gss)" && return SAVE_UMASK=`umask` umask 0022 diff --git a/lustre/tests/sanityN.sh b/lustre/tests/sanityN.sh index a3fffce..aa70f83 100644 --- a/lustre/tests/sanityN.sh +++ b/lustre/tests/sanityN.sh @@ -41,6 +41,10 @@ LUSTRE=${LUSTRE:-`dirname $0`/..} init_test_env $@ . ${CONFIG:=$LUSTRE/tests/cfg/local.sh} +if [ ! -z "$USING_KRB5" ]; then + $RUNAS krb5_login.sh || exit 1 +fi + cleanup() { echo -n "cln.." grep " $MOUNT2 " /proc/mounts && zconf_umount `hostname` $MOUNT2 ${FORCE} diff --git a/lustre/tests/test-framework.sh b/lustre/tests/test-framework.sh index dfc8daa..c3e147f 100644 --- a/lustre/tests/test-framework.sh +++ b/lustre/tests/test-framework.sh @@ -38,17 +38,24 @@ init_test_env() { [ -d /r ] && export ROOT=${ROOT:-/r} export TMP=${TMP:-$ROOT/tmp} - export PATH=:$PATH:$LUSTRE/utils:$LUSTRE/tests + export PATH=:$PATH:$LUSTRE/utils:$LUSTRE/utils/gss:$LUSTRE/tests export LCTL=${LCTL:-"$LUSTRE/utils/lctl"} export MKFS=${MKFS:-"$LUSTRE/utils/mkfs.lustre"} export CHECKSTAT="${CHECKSTAT:-checkstat} " export FSYTPE=${FSTYPE:-"ldiskfs"} export LPROC=/proc/fs/lustre + export LGSSD=${LGSSD:-"$LUSTRE/utils/gss/lgssd"} + export LSVCGSSD=${LSVCGSSD:-"$LUSTRE/utils/gss/lsvcgssd"} + export KRB5DIR=${KRB5DIR:-"/usr/kerberos"} if [ "$ACCEPTOR_PORT" ]; then export PORT_OPT="--port $ACCEPTOR_PORT" fi + if [ "x$SEC" = "xkrb5i" -o "x$SEC" = "xkrb5p" ]; then + export USING_KRB5="y" + fi + # Paths on remote nodes, if different export RLUSTRE=${RLUSTRE:-$LUSTRE} export RPWD=${RPWD:-$PWD} @@ -78,7 +85,12 @@ load_module() { insmod ${LUSTRE}/${module}${EXT} $@ else # must be testing a "make install" or "rpm" installation - modprobe $BASE $@ + # note failed to load ptlrpc_gss is considered not fatal + if [ "$BASE" == "ptlrpc_gss" ]; then + modprobe $BASE $@ || echo "gss/krb5 is not supported" + else + modprobe $BASE $@ + fi fi } @@ -102,6 +114,7 @@ load_modules() { load_module lvfs/lvfs load_module obdclass/obdclass load_module ptlrpc/ptlrpc + load_module ptlrpc/gss/ptlrpc_gss load_module fid/fid load_module fld/fld load_module lmv/lmv @@ -165,6 +178,76 @@ unload_modules() { return 0 } +check_gss_daemon_facet() { + facet=$1 + dname=$2 + + num=`do_facet $facet ps -o cmd -C $dname | grep $dname | wc -l` + if [ $num -ne 1 ]; then + echo "$num instance of $dname on $facet" + return 1 + fi + return 0 +} + +send_sigint() { + local facet=$1 + shift + do_facet $facet "killall -2 $@ 2>/dev/null || true" +} + +start_gss_daemons() { + # starting on MDT + do_facet mds "$LSVCGSSD -v" + do_facet mds "$LGSSD -v" + # starting on OSTs + for num in `seq $OSTCOUNT`; do + do_facet ost$num "$LSVCGSSD -v" + done + # starting on client + # FIXME: is "client" the right facet name? + do_facet client "$LGSSD -v" + + # wait daemons entering "stable" status + sleep 5 + + # + # check daemons are running + # + check_gss_daemon_facet mds lsvcgssd + check_gss_daemon_facet mds lgssd + for num in `seq $OSTCOUNT`; do + check_gss_daemon_facet ost$num lsvcgssd + done + check_gss_daemon_facet client lgssd +} + +stop_gss_daemons() { + send_sigint mds lsvcgssd lgssd + for num in `seq $OSTCOUNT`; do + send_sigint ost$num lsvcgssd + done + send_sigint client lgssd +} + +init_krb5_env() { + if [ ! -z $SEC ]; then + MDS_MOUNT_OPTS=$MDS_MOUNT_OPTS,sec=$SEC + OST_MOUNT_OPTS=$OST_MOUNT_OPTS,sec=$SEC + fi + + if [ ! -z $USING_KRB5 ]; then + start_gss_daemons + fi +} + +cleanup_krb5_env() { + if [ ! -z $USING_KRB5 ]; then + stop_gss_daemons + # maybe cleanup credential cache? + fi +} + # Facet functions # start facet device options start() { @@ -549,6 +632,7 @@ stopall() { cleanupall() { stopall $* unload_modules + cleanup_krb5_env } formatall() { @@ -577,6 +661,7 @@ mount_client() { setupall() { load_modules + init_krb5_env echo Setup mdt, osts start mds $MDSDEV $MDS_MOUNT_OPTS for num in `seq $OSTCOUNT`; do diff --git a/lustre/utils/Makefile.am b/lustre/utils/Makefile.am index 5648aee..8eb36df 100644 --- a/lustre/utils/Makefile.am +++ b/lustre/utils/Makefile.am @@ -1,5 +1,9 @@ # Administration utilities Makefile +if GSS +SUBDIRS = gss +endif + AM_CFLAGS=$(LLCFLAGS) AM_CPPFLAGS=$(LLCPPFLAGS) -DLUSTRE_UTILS=1 AM_LDFLAGS := -L$(top_builddir)/lnet/utils diff --git a/lustre/utils/gss/.cvsignore b/lustre/utils/gss/.cvsignore new file mode 100644 index 0000000..65a8e0a --- /dev/null +++ b/lustre/utils/gss/.cvsignore @@ -0,0 +1,10 @@ +.Xrefs +Makefile +Makefile.in +.deps +tags +TAGS +lgssd +lsvcgssd +.*.cmd +.*.d diff --git a/lustre/utils/gss/Makefile.am b/lustre/utils/gss/Makefile.am new file mode 100644 index 0000000..479e28d --- /dev/null +++ b/lustre/utils/gss/Makefile.am @@ -0,0 +1,59 @@ +# gss daemons Makefile + +SUBDIRS = + +AM_CFLAGS=$(LLCFLAGS) +AM_CPPFLAGS=$(LLCPPFLAGS) -DLUSTRE_UTILS=1 +AM_LDFLAGS := -L$(top_builddir)/lnet/utils + +LIBPTLCTL := $(top_builddir)/lnet/utils/libptlctl.a + +sbin_PROGRAMS = lgssd lsvcgssd + +COMMON_SRCS = \ + context.c \ + context_mit.c \ + context_heimdal.c \ + context_spkm3.c \ + gss_util.c \ + gss_oids.c \ + err_util.c \ + lsupport.c \ + \ + context.h \ + err_util.h \ + gss_oids.h \ + gss_util.h \ + lsupport.h + +lgssd_SOURCES = \ + $(COMMON_SRCS) \ + gssd.c \ + gssd_main_loop.c \ + gssd_proc.c \ + krb5_util.c \ + \ + gssd.h \ + krb5_util.h \ + write_bytes.h + +lgssd_LDADD = $(GSSAPI_LIBS) $(KRBLIBS) +lgssd_LDFLAGS = $(KRBLDFLAGS) +lgssd_CFLAGS = $(AM_CFLAGS) $(CFLAGS) $(KRBCFLAGS) + +lsvcgssd_SOURCES = \ + $(COMMON_SRCS) \ + cacheio.c \ + svcgssd.c \ + svcgssd_main_loop.c \ + svcgssd_mech2file.c \ + svcgssd_proc.c \ + \ + cacheio.h \ + svcgssd.h + +lsvcgssd_LDADD = $(GSSAPI_LIBS) $(KRBLIBS) +lsvcgssd_LDFLAGS = $(KRBLDFLAGS) +lsvcgssd_CFLAGS = $(AM_CFLAGS) $(CFLAGS) $(KRBCFLAGS) + +EXTRA_DIST = diff --git a/lustre/utils/gss/README b/lustre/utils/gss/README new file mode 100644 index 0000000..77a6662 --- /dev/null +++ b/lustre/utils/gss/README @@ -0,0 +1,12 @@ +lustre/utils/gss: client & server side gss daemons for Lustre. + +All files came from standard nfs-utils package, applied with patches +created by Cluster File Systems Inc. + +1. Stock nfs-utils-1.0.10.tgz +2. Apply nfs-utils-1.0.10-CITI_NFS4_ALL-1.dif from Center for Information + Technology Integration, University of Michigan + (http://www.citi.umich.edu/projects/nfsv4/linux/) +3. Apply lustre patch: nfs-utils-1.0.10-lustre.diff +4. Copy nfs-utils-1.0.10/aclocal/kerberos5.m4 to lustre/autoconf +5. Copy nfs-utils-1.0.10/utils/gssd/*.[ch] to here diff --git a/lustre/utils/gss/cacheio.c b/lustre/utils/gss/cacheio.c new file mode 100644 index 0000000..3b39316 --- /dev/null +++ b/lustre/utils/gss/cacheio.c @@ -0,0 +1,296 @@ +/* + 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. +*/ + +/* + * support/nfs/cacheio.c + * support IO on the cache channel files in 2.5 and beyond. + * These use 'qwords' which are like words, but with a little quoting. + * + */ + + +/* + * Support routines for text-based upcalls. + * Fields are separated by spaces. + * Fields are either mangled to quote space tab newline slosh with slosh + * or a hexified with a leading \x + * Record is terminated with newline. + * + */ + +#include "cacheio.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "err_util.h" + +void qword_add(char **bpp, int *lp, char *str) +{ + char *bp = *bpp; + int len = *lp; + char c; + + if (len < 0) return; + + while ((c=*str++) && len) + switch(c) { + case ' ': + case '\t': + case '\n': + case '\\': + if (len >= 4) { + *bp++ = '\\'; + *bp++ = '0' + ((c & 0300)>>6); + *bp++ = '0' + ((c & 0070)>>3); + *bp++ = '0' + ((c & 0007)>>0); + } + len -= 4; + break; + default: + *bp++ = c; + len--; + } + if (c || len <1) len = -1; + else { + *bp++ = ' '; + len--; + } + *bpp = bp; + *lp = len; +} + +void qword_addhex(char **bpp, int *lp, char *buf, int blen) +{ + char *bp = *bpp; + int len = *lp; + + if (len < 0) return; + + if (len > 2) { + *bp++ = '\\'; + *bp++ = 'x'; + len -= 2; + while (blen && len >= 2) { + unsigned char c = *buf++; + *bp++ = '0' + ((c&0xf0)>>4) + (c>=0xa0)*('a'-'9'-1); + *bp++ = '0' + (c&0x0f) + ((c&0x0f)>=0x0a)*('a'-'9'-1); + len -= 2; + blen--; + } + } + if (blen || len<1) len = -1; + else { + *bp++ = ' '; + len--; + } + *bpp = bp; + *lp = len; +} + +void qword_addint(char **bpp, int *lp, int n) +{ + int len; + + len = snprintf(*bpp, *lp, "%d ", n); + if (len > *lp) + len = *lp; + *bpp += len; + *lp -= len; +} + +void qword_addeol(char **bpp, int *lp) +{ + if (*lp <= 0) + return; + **bpp = '\n'; + (*bpp)++; + (*lp)--; +} + +static char qword_buf[8192]; +static char tmp_buf[8192]; +void qword_print(FILE *f, char *str) +{ + char *bp = qword_buf; + int len = sizeof(qword_buf); + qword_add(&bp, &len, str); + fwrite(qword_buf, bp-qword_buf, 1, f); + /* XXX: */ + memcpy(tmp_buf, qword_buf, bp-qword_buf); + tmp_buf[bp-qword_buf] = '\0'; + printerr(2, "%s", tmp_buf); +} + +void qword_printhex(FILE *f, char *str, int slen) +{ + char *bp = qword_buf; + int len = sizeof(qword_buf); + qword_addhex(&bp, &len, str, slen); + fwrite(qword_buf, bp-qword_buf, 1, f); + /* XXX: */ + memcpy(tmp_buf, qword_buf, bp-qword_buf); + tmp_buf[bp-qword_buf] = '\0'; + printerr(2, "%s", tmp_buf); +} + +void qword_printint(FILE *f, int num) +{ + fprintf(f, "%d ", num); + printerr(2, "%d ", num); +} + +void qword_eol(FILE *f) +{ + fprintf(f,"\n"); + fflush(f); + printerr(2, "\n"); +} + + + +#define isodigit(c) (isdigit(c) && c <= '7') +int qword_get(char **bpp, char *dest, int bufsize) +{ + /* return bytes copied, or -1 on error */ + char *bp = *bpp; + int len = 0; + + while (*bp == ' ') bp++; + + if (bp[0] == '\\' && bp[1] == 'x') { + /* HEX STRING */ + bp += 2; + while (isxdigit(bp[0]) && isxdigit(bp[1]) && len < bufsize) { + int byte = isdigit(*bp) ? *bp-'0' : toupper(*bp)-'A'+10; + bp++; + byte <<= 4; + byte |= isdigit(*bp) ? *bp-'0' : toupper(*bp)-'A'+10; + *dest++ = byte; + bp++; + len++; + } + } else { + /* text with \nnn octal quoting */ + while (*bp != ' ' && *bp != '\n' && *bp && len < bufsize-1) { + if (*bp == '\\' && + isodigit(bp[1]) && (bp[1] <= '3') && + isodigit(bp[2]) && + isodigit(bp[3])) { + int byte = (*++bp -'0'); + bp++; + byte = (byte << 3) | (*bp++ - '0'); + byte = (byte << 3) | (*bp++ - '0'); + *dest++ = byte; + len++; + } else { + *dest++ = *bp++; + len++; + } + } + } + + if (*bp != ' ' && *bp != '\n' && *bp != '\0') + return -1; + while (*bp == ' ') bp++; + *bpp = bp; +// why should we clear *dest??? +// *dest = '\0'; + return len; +} + +int qword_get_int(char **bpp, int *anint) +{ + char buf[50]; + char *ep; + int rv; + int len = qword_get(bpp, buf, 50); + if (len < 0) return -1; + if (len ==0) return -1; + rv = strtol(buf, &ep, 0); + if (*ep) return -1; + *anint = rv; + return 0; +} + +#define READLINE_BUFFER_INCREMENT 2048 + +int readline(int fd, char **buf, int *lenp) +{ + /* read a line into *buf, which is malloced *len long + * realloc if needed until we find a \n + * nul out the \n and return + * 0 of eof, 1 of success + */ + int len; + + if (*lenp == 0) { + char *b = malloc(READLINE_BUFFER_INCREMENT); + if (b == NULL) + return 0; + *buf = b; + *lenp = READLINE_BUFFER_INCREMENT; + } + len = read(fd, *buf, *lenp); + if (len <= 0) { + printerr(0, "readline: read error: len %d errno %d (%s)\n", + len, errno, strerror(errno)); + return 0; + } + while ((*buf)[len-1] != '\n') { + /* now the less common case. There was no newline, + * so we have to keep reading after re-alloc + */ + char *new; + int nl; + *lenp += READLINE_BUFFER_INCREMENT; + new = realloc(*buf, *lenp); + if (new == NULL) + return 0; + *buf = new; + nl = read(fd, *buf +len, *lenp - len); + if (nl <= 0 ) { + printerr(0, "readline: read error: len %d " + "errno %d (%s)\n", nl, errno, strerror(errno)); + return 0; + } + len += nl; + } + (*buf)[len-1] = 0; + printerr(3, "readline: read %d chars into buffer of size %d:\n%s\n", + len, *lenp, *buf); + return 1; +} diff --git a/lustre/utils/gss/cacheio.h b/lustre/utils/gss/cacheio.h new file mode 100644 index 0000000..cc97b36 --- /dev/null +++ b/lustre/utils/gss/cacheio.h @@ -0,0 +1,48 @@ +/* + 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 _CACHEIO_H_ +#define _CACHEIO_H_ + +#include + +void qword_add(char **bpp, int *lp, char *str); +void qword_addhex(char **bpp, int *lp, char *buf, int blen); +void qword_addint(char **bpp, int *lp, int n); +void qword_addeol(char **bpp, int *lp); +void qword_print(FILE *f, char *str); +void qword_printhex(FILE *f, char *str, int slen); +void qword_printint(FILE *f, int num); +void qword_eol(FILE *f); +int readline(int fd, char **buf, int *lenp); +int qword_get(char **bpp, char *dest, int bufsize); +int qword_get_int(char **bpp, int *anint); + +#endif /* _CACHEIO_H_ */ diff --git a/lustre/utils/gss/context.c b/lustre/utils/gss/context.c new file mode 100644 index 0000000..5f347bb --- /dev/null +++ b/lustre/utils/gss/context.c @@ -0,0 +1,57 @@ +/* + 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. +*/ + +#include "config.h" +#include +#include +#include +#include +#include "gss_util.h" +#include "gss_oids.h" +#include "err_util.h" +#include "context.h" + +int +serialize_context_for_kernel(gss_ctx_id_t ctx, + gss_buffer_desc *buf, + gss_OID mech) +{ + if (g_OID_equal(&krb5oid, mech)) + return serialize_krb5_ctx(ctx, buf); +#ifdef HAVE_SPKM3_H + else if (g_OID_equal(&spkm3oid, mech)) + return serialize_spkm3_ctx(ctx, buf); +#endif + else { + printerr(0, "ERROR: attempting to serialize context with " + "unknown/unsupported mechanism oid\n"); + return -1; + } +} diff --git a/lustre/utils/gss/context.h b/lustre/utils/gss/context.h new file mode 100644 index 0000000..71638b8 --- /dev/null +++ b/lustre/utils/gss/context.h @@ -0,0 +1,39 @@ +/* + 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 _CONTEXT_H_ +#define _CONTEXT_H_ + +int serialize_context_for_kernel(gss_ctx_id_t ctx, gss_buffer_desc *buf, + gss_OID mech); +int serialize_spkm3_ctx(gss_ctx_id_t ctx, gss_buffer_desc *buf); +int serialize_krb5_ctx(gss_ctx_id_t ctx, gss_buffer_desc *buf); + +#endif /* _CONTEXT_H_ */ diff --git a/lustre/utils/gss/context_heimdal.c b/lustre/utils/gss/context_heimdal.c new file mode 100644 index 0000000..edd4dfc --- /dev/null +++ b/lustre/utils/gss/context_heimdal.c @@ -0,0 +1,267 @@ +/* + 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. +*/ + +#include "config.h" + +#ifdef HAVE_HEIMDAL + +#include +#include +#include +#include +#include +#include +#include /* Must use the heimdal copy! */ +#ifdef HAVE_COM_ERR_H +#include +#endif +#include "err_util.h" +#include "gss_oids.h" +#include "write_bytes.h" + +#define MAX_CTX_LEN 4096 + +int write_heimdal_keyblock(char **p, char *end, krb5_keyblock *key) +{ + gss_buffer_desc tmp; + int code = -1; + + if (WRITE_BYTES(p, end, key->keytype)) goto out_err; + tmp.length = key->keyvalue.length; + tmp.value = key->keyvalue.data; + if (write_buffer(p, end, &tmp)) goto out_err; + code = 0; + out_err: + return(code); +} + +int write_heimdal_enc_key(char **p, char *end, gss_ctx_id_t ctx) +{ + krb5_keyblock enc_key, *key; + krb5_context context; + krb5_error_code ret; + int i; + char *skd, *dkd; + int code = -1; + + if ((ret = krb5_init_context(&context))) { + printerr(0, "ERROR: initializing krb5_context: %s\n", + error_message(ret)); + goto out_err; + } + + if ((ret = krb5_auth_con_getlocalsubkey(context, + ctx->auth_context, &key))){ + printerr(0, "ERROR: getting auth_context key: %s\n", + error_message(ret)); + goto out_err_free_context; + } + + memset(&enc_key, 0, sizeof(enc_key)); + enc_key.keytype = key->keytype; + /* XXX current kernel code only handles des-cbc-raw (4) */ + if (enc_key.keytype != 4) { + printerr(1, "WARN: write_heimdal_enc_key: " + "overriding heimdal keytype (%d => %d)\n", + enc_key.keytype, 4); + enc_key.keytype = 4; + } + enc_key.keyvalue.length = key->keyvalue.length; + if ((enc_key.keyvalue.data = + calloc(1, enc_key.keyvalue.length)) == NULL) { + + printerr(0, "ERROR: allocating memory for enc key: %s\n", + error_message(ENOMEM)); + goto out_err_free_key; + } + skd = (char *) key->keyvalue.data; + dkd = (char *) enc_key.keyvalue.data; + for (i = 0; i < enc_key.keyvalue.length; i++) + dkd[i] = skd[i] ^ 0xf0; + if (write_heimdal_keyblock(p, end, &enc_key)) { + goto out_err_free_enckey; + } + + code = 0; + + out_err_free_enckey: + krb5_free_keyblock_contents(context, &enc_key); + out_err_free_key: + krb5_free_keyblock(context, key); + out_err_free_context: + krb5_free_context(context); + out_err: + printerr(2, "write_heimdal_enc_key: %s\n", code ? "FAILED" : "SUCCESS"); + return(code); +} + +int write_heimdal_seq_key(char **p, char *end, gss_ctx_id_t ctx) +{ + krb5_keyblock *key; + krb5_context context; + krb5_error_code ret; + int code = -1; + + if ((ret = krb5_init_context(&context))) { + printerr(0, "ERROR: initializing krb5_context: %s\n", + error_message(ret)); + goto out_err; + } + + if ((ret = krb5_auth_con_getlocalsubkey(context, + ctx->auth_context, &key))){ + printerr(0, "ERROR: getting auth_context key: %s\n", + error_message(ret)); + goto out_err_free_context; + } + + /* XXX current kernel code only handles des-cbc-raw (4) */ + if (key->keytype != 4) { + printerr(1, "WARN: write_heimdal_seq_key: " + "overriding heimdal keytype (%d => %d)\n", + key->keytype, 4); + key->keytype = 4; + } + + if (write_heimdal_keyblock(p, end, key)) { + goto out_err_free_key; + } + + code = 0; + + out_err_free_key: + krb5_free_keyblock(context, key); + out_err_free_context: + krb5_free_context(context); + out_err: + printerr(2, "write_heimdal_seq_key: %s\n", code ? "FAILED" : "SUCCESS"); + return(code); +} + +/* + * The following is the kernel structure that we are filling in: + * + * struct krb5_ctx { + * int initiate; + * int seed_init; + * unsigned char seed[16]; + * int signalg; + * int sealalg; + * struct crypto_tfm *enc; + * struct crypto_tfm *seq; + * s32 endtime; + * u32 seq_send; + * struct xdr_netobj mech_used; + * }; + * + * However, note that we do not send the data fields in the + * order they appear in the structure. The order they are + * sent down in is: + * + * initiate + * seed_init + * seed + * signalg + * sealalg + * endtime + * seq_send + * mech_used + * enc key + * seq key + * + */ + +int +serialize_krb5_ctx(gss_ctx_id_t ctx, gss_buffer_desc *buf) +{ + + char *p, *end; + static int constant_one = 1; + static int constant_zero = 0; + unsigned char fakeseed[16]; + uint32_t algorithm; + + if (!(buf->value = calloc(1, MAX_CTX_LEN))) + goto out_err; + p = buf->value; + end = buf->value + MAX_CTX_LEN; + + + /* initiate: 1 => initiating 0 => accepting */ + if (ctx->more_flags & LOCAL) { + if (WRITE_BYTES(&p, end, constant_one)) goto out_err; + } + else { + if (WRITE_BYTES(&p, end, constant_zero)) goto out_err; + } + + /* seed_init: not used by kernel code */ + if (WRITE_BYTES(&p, end, constant_zero)) goto out_err; + + /* seed: not used by kernel code */ + memset(&fakeseed, 0, sizeof(fakeseed)); + if (write_bytes(&p, end, &fakeseed, 16)) goto out_err; + + /* signalg */ + algorithm = 0; /* SGN_ALG_DES_MAC_MD5 XXX */ + if (WRITE_BYTES(&p, end, algorithm)) goto out_err; + + /* sealalg */ + algorithm = 0; /* SEAL_ALG_DES XXX */ + if (WRITE_BYTES(&p, end, algorithm)) goto out_err; + + /* endtime */ + if (WRITE_BYTES(&p, end, ctx->lifetime)) goto out_err; + + /* seq_send */ + if (WRITE_BYTES(&p, end, ctx->auth_context->local_seqnumber)) + goto out_err; + /* mech_used */ + if (write_buffer(&p, end, (gss_buffer_desc*)&krb5oid)) goto out_err; + + /* enc: derive the encryption key and copy it into buffer */ + if (write_heimdal_enc_key(&p, end, ctx)) goto out_err; + + /* seq: get the sequence number key and copy it into buffer */ + if (write_heimdal_seq_key(&p, end, ctx)) goto out_err; + + buf->length = p - (char *)buf->value; + printerr(2, "serialize_krb5_ctx: returning buffer " + "with %d bytes\n", buf->length); + + return 0; +out_err: + printerr(0, "ERROR: failed exporting Heimdal krb5 ctx to kernel\n"); + if (buf->value) free(buf->value); + buf->length = 0; + return -1; +} + +#endif /* HAVE_HEIMDAL */ diff --git a/lustre/utils/gss/context_mit.c b/lustre/utils/gss/context_mit.c new file mode 100644 index 0000000..164ca92 --- /dev/null +++ b/lustre/utils/gss/context_mit.c @@ -0,0 +1,817 @@ +/* + 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. +*/ + +#include "config.h" +#include +#include +#include +#include +#include +#include "gss_util.h" +#include "gss_oids.h" +#include "err_util.h" +#include "context.h" + +#ifdef HAVE_KRB5 +#include + +/* for 3DES */ +#define KG_USAGE_SEAL 22 +#define KG_USAGE_SIGN 23 +#define KG_USAGE_SEQ 24 + +/* for rfc???? */ +#define KG_USAGE_ACCEPTOR_SEAL 22 +#define KG_USAGE_ACCEPTOR_SIGN 23 +#define KG_USAGE_INITIATOR_SEAL 24 +#define KG_USAGE_INITIATOR_SIGN 25 + +/* Lifted from mit src/lib/gssapi/krb5/gssapiP_krb5.h */ +enum seal_alg { + SEAL_ALG_NONE = 0xffff, + SEAL_ALG_DES = 0x0000, + SEAL_ALG_1 = 0x0001, /* not published */ + SEAL_ALG_MICROSOFT_RC4 = 0x0010, /* microsoft w2k; */ + SEAL_ALG_DES3KD = 0x0002 +}; + +#define KEY_USAGE_SEED_ENCRYPTION 0xAA +#define KEY_USAGE_SEED_INTEGRITY 0x55 +#define KEY_USAGE_SEED_CHECKSUM 0x99 +#define K5CLENGTH 5 + +/* Flags for version 2 context flags */ +#define KRB5_CTX_FLAG_INITIATOR 0x00000001 +#define KRB5_CTX_FLAG_CFX 0x00000002 +#define KRB5_CTX_FLAG_ACCEPTOR_SUBKEY 0x00000004 + +/* + * XXX Hack alert. We don't have "legal" access to these + * structures located in libk5crypto + */ +extern void krb5int_enc_arcfour; +extern void krb5int_enc_des3; +extern void krb5int_enc_aes128; +extern void krb5int_enc_aes256; +extern int krb5_derive_key(); + +void *get_enc_provider(); + +/* XXX spkm3 seems to actually want it this big, yipes. */ +#define MAX_CTX_LEN 4096 + + + +#ifdef HAVE_LUCID_CONTEXT_SUPPORT + +/* Don't use the private structure, use the exported lucid structure */ +#include + +#elif (KRB5_VERSION > 131) +/* XXX argggg, there's gotta be a better way than just duplicating this + * whole struct. Unfortunately, this is in a "private" header file, + * so this is our best choice at this point :-/ + * + * XXX Does this match the Heimdal definition? */ + +typedef struct _krb5_gss_ctx_id_rec { + unsigned int initiate : 1; /* nonzero if initiating, zero if accepting */ + unsigned int established : 1; + unsigned int big_endian : 1; + unsigned int have_acceptor_subkey : 1; + unsigned int seed_init : 1; /* XXX tested but never actually set */ +#ifdef CFX_EXERCISE + unsigned int testing_unknown_tokid : 1; /* for testing only */ +#endif + OM_uint32 gss_flags; + unsigned char seed[16]; + krb5_principal here; + krb5_principal there; + krb5_keyblock *subkey; + int signalg; + size_t cksum_size; + int sealalg; + krb5_keyblock *enc; + krb5_keyblock *seq; + krb5_timestamp endtime; + krb5_flags krb_flags; + /* XXX these used to be signed. the old spec is inspecific, and + the new spec specifies unsigned. I don't believe that the change + affects the wire encoding. */ + uint64_t seq_send; /* gssint_uint64 */ + uint64_t seq_recv; /* gssint_uint64 */ + void *seqstate; + krb5_auth_context auth_context; + gss_OID_desc *mech_used; /* gss_OID_desc */ + /* Protocol spec revision + 0 => RFC 1964 with 3DES and RC4 enhancements + 1 => draft-ietf-krb-wg-gssapi-cfx-01 + No others defined so far. */ + int proto; + krb5_cksumtype cksumtype; /* for "main" subkey */ + krb5_keyblock *acceptor_subkey; /* CFX only */ + krb5_cksumtype acceptor_subkey_cksumtype; +#ifdef CFX_EXERCISE + gss_buffer_desc init_token; +#endif +} krb5_gss_ctx_id_rec, *krb5_gss_ctx_id_t; + +#else /* KRB5_VERSION > 131 */ + +typedef struct _krb5_gss_ctx_id_rec { + int initiate; + u_int32_t gss_flags; + int seed_init; + unsigned char seed[16]; + krb5_principal here; + krb5_principal there; + krb5_keyblock *subkey; + int signalg; + int cksum_size; + int sealalg; + krb5_keyblock *enc; + krb5_keyblock *seq; + krb5_timestamp endtime; + krb5_flags krb_flags; + krb5_ui_4 seq_send; + krb5_ui_4 seq_recv; + void *seqstate; + int established; + int big_endian; + krb5_auth_context auth_context; + gss_OID_desc *mech_used; + int nctypes; + krb5_cksumtype *ctypes; +} krb5_gss_ctx_id_rec, *krb5_gss_ctx_id_t; + +#endif /* KRB5_VERSION */ + + +#ifdef HAVE_LUCID_CONTEXT_SUPPORT /* Lucid context support */ +static int +write_lucid_keyblock(char **p, char *end, gss_krb5_lucid_key_t *key) +{ + gss_buffer_desc tmp; + + if (WRITE_BYTES(p, end, key->type)) return -1; + tmp.length = key->length; + tmp.value = key->data; + if (write_buffer(p, end, &tmp)) return -1; + return 0; +} + +static void +key_lucid_to_krb5(const gss_krb5_lucid_key_t *lin, krb5_keyblock *kout) +{ + memset(kout, '\0', sizeof(kout)); + kout->enctype = lin->type; + kout->length = lin->length; + kout->contents = lin->data; +} + +static void +key_krb5_to_lucid(const krb5_keyblock *kin, gss_krb5_lucid_key_t *lout) +{ + memset(lout, '\0', sizeof(lout)); + lout->type = kin->enctype; + lout->length = kin->length; + lout->data = kin->contents; +} + +/* + * Function to derive a new key from a given key and given constant data. + */ +static krb5_error_code +derive_key_lucid(const gss_krb5_lucid_key_t *in, gss_krb5_lucid_key_t *out, + int usage, char extra) +{ + krb5_error_code code; + unsigned char constant_data[K5CLENGTH]; + krb5_data datain; + int keylength; + void *enc; + krb5_keyblock kin, kout; /* must send krb5_keyblock, not lucid! */ + + /* + * XXX Hack alert. We don't have "legal" access to these + * values and structures located in libk5crypto + */ + switch (in->type) { + case ENCTYPE_DES3_CBC_RAW: + keylength = 24; + enc = &krb5int_enc_des3; + break; + case ENCTYPE_AES128_CTS_HMAC_SHA1_96: + keylength = 16; + enc = &krb5int_enc_aes128; + break; + case ENCTYPE_AES256_CTS_HMAC_SHA1_96: + keylength = 32; + enc = &krb5int_enc_aes256; + break; + default: + code = KRB5_BAD_ENCTYPE; + goto out; + } + + /* allocate memory for output key */ + if ((out->data = malloc(keylength)) == NULL) { + code = ENOMEM; + goto out; + } + out->length = keylength; + out->type = in->type; + + /* Convert to correct format for call to krb5_derive_key */ + key_lucid_to_krb5(in, &kin); + key_lucid_to_krb5(out, &kout); + + datain.data = (char *) constant_data; + datain.length = K5CLENGTH; + + datain.data[0] = (usage>>24)&0xff; + datain.data[1] = (usage>>16)&0xff; + datain.data[2] = (usage>>8)&0xff; + datain.data[3] = usage&0xff; + + datain.data[4] = (char) extra; + + if ((code = krb5_derive_key(enc, &kin, &kout, &datain))) { + free(out->data); + out->data = NULL; + goto out; + } + key_krb5_to_lucid(&kout, out); + + out: + if (code) + printerr(0, "ERROR: derive_key_lucid returning error %d (%s)\n", + code, error_message(code)); + return (code); +} + +static int +prepare_krb5_rfc1964_buffer(gss_krb5_lucid_context_v1_t *lctx, + gss_buffer_desc *buf) +{ + char *p, *end; + static int constant_zero = 0; + unsigned char fakeseed[16]; + uint32_t word_send_seq; + gss_krb5_lucid_key_t enc_key; + int i; + char *skd, *dkd; + gss_buffer_desc fakeoid; + + /* + * The new Kerberos interface to get the gss context + * does not include the seed or seed_init fields + * because we never really use them. But for now, + * send down a fake buffer so we can use the same + * interface to the kernel. + */ + memset(&enc_key, 0, sizeof(enc_key)); + memset(&fakeoid, 0, sizeof(fakeoid)); + + if (!(buf->value = calloc(1, MAX_CTX_LEN))) + goto out_err; + p = buf->value; + end = buf->value + MAX_CTX_LEN; + + if (WRITE_BYTES(&p, end, lctx->initiate)) goto out_err; + + /* seed_init and seed not used by kernel anyway */ + if (WRITE_BYTES(&p, end, constant_zero)) goto out_err; + if (write_bytes(&p, end, &fakeseed, 16)) goto out_err; + + if (WRITE_BYTES(&p, end, lctx->rfc1964_kd.sign_alg)) goto out_err; + if (WRITE_BYTES(&p, end, lctx->rfc1964_kd.seal_alg)) goto out_err; + if (WRITE_BYTES(&p, end, lctx->endtime)) goto out_err; + word_send_seq = lctx->send_seq; /* XXX send_seq is 64-bit */ + if (WRITE_BYTES(&p, end, word_send_seq)) goto out_err; + if (write_oid(&p, end, &krb5oid)) goto out_err; + + printerr(2, "prepare_krb5_rfc1964_buffer: serializing keys with " + "enctype %d and length %d\n", + lctx->rfc1964_kd.ctx_key.type, + lctx->rfc1964_kd.ctx_key.length); + + /* derive the encryption key and copy it into buffer */ + enc_key.type = lctx->rfc1964_kd.ctx_key.type; + enc_key.length = lctx->rfc1964_kd.ctx_key.length; + if ((enc_key.data = calloc(1, enc_key.length)) == NULL) + goto out_err; + skd = (char *) lctx->rfc1964_kd.ctx_key.data; + dkd = (char *) enc_key.data; + for (i = 0; i < enc_key.length; i++) + dkd[i] = skd[i] ^ 0xf0; + if (write_lucid_keyblock(&p, end, &enc_key)) { + free(enc_key.data); + goto out_err; + } + free(enc_key.data); + + if (write_lucid_keyblock(&p, end, &lctx->rfc1964_kd.ctx_key)) + goto out_err; + + buf->length = p - (char *)buf->value; + return 0; +out_err: + printerr(0, "ERROR: failed serializing krb5 context for kernel\n"); + if (buf->value) { + free(buf->value); + buf->value = NULL; + } + buf->length = 0; + if (enc_key.data) { + free(enc_key.data); + enc_key.data = NULL; + } + return -1; +} + +/* + * Prepare a new-style buffer to send to the kernel for newer encryption + * types -- or for DES3. + * + * The new format is: + * + * u32 version; This is two (2) + * s32 endtime; + * u32 flags; + * #define KRB5_CTX_FLAG_INITIATOR 0x00000001 + * #define KRB5_CTX_FLAG_CFX 0x00000002 + * #define KRB5_CTX_FLAG_ACCEPTOR_SUBKEY 0x00000004 + * u64 seq_send; + * u32 enctype; ( encrption type of keys ) + * u32 size_of_each_key; ( size of each key in bytes ) + * u32 number_of_keys; ( N -- should always be 3 for now ) + * keydata-1; ( Ke ) + * keydata-2; ( Ki ) + * keydata-3; ( Kc ) + * + */ +static int +prepare_krb5_ctx_v2_buffer(gss_krb5_lucid_context_v1_t *lctx, + gss_buffer_desc *buf) +{ + char *p, *end; + static uint32_t version = 2; + uint32_t v2_flags = 0; + gss_krb5_lucid_key_t enc_key; + gss_krb5_lucid_key_t derived_key; + gss_buffer_desc fakeoid; + uint32_t enctype; + uint32_t keysize; + uint32_t numkeys; + + memset(&enc_key, 0, sizeof(enc_key)); + memset(&fakeoid, 0, sizeof(fakeoid)); + + if (!(buf->value = calloc(1, MAX_CTX_LEN))) + goto out_err; + p = buf->value; + end = buf->value + MAX_CTX_LEN; + + /* Version 2 */ + if (WRITE_BYTES(&p, end , version)) goto out_err; + if (WRITE_BYTES(&p, end, lctx->endtime)) goto out_err; + + if (lctx->initiate) + v2_flags |= KRB5_CTX_FLAG_INITIATOR; + if (lctx->protocol != 0) + v2_flags |= KRB5_CTX_FLAG_CFX; + if (lctx->protocol != 0 && lctx->cfx_kd.have_acceptor_subkey == 1) + v2_flags |= KRB5_CTX_FLAG_ACCEPTOR_SUBKEY; + + if (WRITE_BYTES(&p, end, v2_flags)) goto out_err; + + if (WRITE_BYTES(&p, end, lctx->send_seq)) goto out_err; + + /* Protocol 0 here implies DES3 or RC4 */ + if (lctx->protocol == 0) { + enctype = lctx->rfc1964_kd.ctx_key.type; + keysize = lctx->rfc1964_kd.ctx_key.length; + numkeys = 3; /* XXX is always gonna be three? */ + } else { + if (lctx->cfx_kd.have_acceptor_subkey) { + enctype = lctx->cfx_kd.acceptor_subkey.type; + keysize = lctx->cfx_kd.acceptor_subkey.length; + } else { + enctype = lctx->cfx_kd.ctx_key.type; + keysize = lctx->cfx_kd.ctx_key.length; + } + numkeys = 3; + } + printerr(2, "prepare_krb5_ctx_v2_buffer: serializing %d keys with " + "enctype %d and size %d\n", numkeys, enctype, keysize); + if (WRITE_BYTES(&p, end, enctype)) goto out_err; + if (WRITE_BYTES(&p, end, keysize)) goto out_err; + if (WRITE_BYTES(&p, end, numkeys)) goto out_err; + + if (lctx->protocol == 0) { + /* derive and send down: Ke, Ki, and Kc */ + /* Ke */ + if (write_bytes(&p, end, lctx->rfc1964_kd.ctx_key.data, + lctx->rfc1964_kd.ctx_key.length)) + goto out_err; + + /* Ki */ + if (write_bytes(&p, end, lctx->rfc1964_kd.ctx_key.data, + lctx->rfc1964_kd.ctx_key.length)) + goto out_err; + + /* Kc */ + if (derive_key_lucid(&lctx->rfc1964_kd.ctx_key, + &derived_key, + KG_USAGE_SIGN, KEY_USAGE_SEED_CHECKSUM)) + goto out_err; + if (write_bytes(&p, end, derived_key.data, + derived_key.length)) + goto out_err; + free(derived_key.data); + } else { + gss_krb5_lucid_key_t *keyptr; + uint32_t sign_usage, seal_usage; + + if (lctx->cfx_kd.have_acceptor_subkey) + keyptr = &lctx->cfx_kd.acceptor_subkey; + else + keyptr = &lctx->cfx_kd.ctx_key; + +#if 0 + if (lctx->initiate == 1) { + sign_usage = KG_USAGE_INITIATOR_SIGN; + seal_usage = KG_USAGE_INITIATOR_SEAL; + } else { + sign_usage = KG_USAGE_ACCEPTOR_SIGN; + seal_usage = KG_USAGE_ACCEPTOR_SEAL; + } +#else + /* FIXME + * These are from rfc4142, but I don't understand: if we supply + * different 'usage' value for client & server, then the peers + * will have different derived keys. How could this work? + * + * Here we simply use old SIGN/SEAL values until we find the + * answer. --ericm + * FIXME + */ + sign_usage = KG_USAGE_SIGN; + seal_usage = KG_USAGE_SEAL; +#endif + + /* derive and send down: Ke, Ki, and Kc */ + + /* Ke */ + if (derive_key_lucid(keyptr, &derived_key, + seal_usage, KEY_USAGE_SEED_ENCRYPTION)) + goto out_err; + if (write_bytes(&p, end, derived_key.data, + derived_key.length)) + goto out_err; + free(derived_key.data); + + /* Ki */ + if (derive_key_lucid(keyptr, &derived_key, + seal_usage, KEY_USAGE_SEED_INTEGRITY)) + goto out_err; + if (write_bytes(&p, end, derived_key.data, + derived_key.length)) + goto out_err; + free(derived_key.data); + + /* Kc */ + if (derive_key_lucid(keyptr, &derived_key, + sign_usage, KEY_USAGE_SEED_CHECKSUM)) + goto out_err; + if (write_bytes(&p, end, derived_key.data, + derived_key.length)) + goto out_err; + free(derived_key.data); + } + + buf->length = p - (char *)buf->value; + return 0; + +out_err: + printerr(0, "ERROR: prepare_krb5_ctx_v2_buffer: " + "failed serializing krb5 context for kernel\n"); + if (buf->value) { + free(buf->value); + buf->value = NULL; + } + buf->length = 0; + if (enc_key.data) { + free(enc_key.data); + enc_key.data = NULL; + } + return -1; +} + + +int +serialize_krb5_ctx(gss_ctx_id_t ctx, gss_buffer_desc *buf) +{ + OM_uint32 maj_stat, min_stat; + void *return_ctx = 0; + OM_uint32 vers; + gss_krb5_lucid_context_v1_t *lctx = 0; + int retcode = 0; + + printerr(2, "DEBUG: serialize_krb5_ctx: lucid version!\n"); + maj_stat = gss_export_lucid_sec_context(&min_stat, &ctx, + 1, &return_ctx); + if (maj_stat != GSS_S_COMPLETE) { + pgsserr("gss_export_lucid_sec_context", + maj_stat, min_stat, &krb5oid); + goto out_err; + } + + /* Check the version returned, we only support v1 right now */ + vers = ((gss_krb5_lucid_context_version_t *)return_ctx)->version; + switch (vers) { + case 1: + lctx = (gss_krb5_lucid_context_v1_t *) return_ctx; + break; + default: + printerr(0, "ERROR: unsupported lucid sec context version %d\n", + vers); + goto out_err; + break; + } + + /* + * Now lctx points to a lucid context that we can send down to kernel + * + * Note: we send down different information to the kernel depending + * on the protocol version and the enctyption type. + * For protocol version 0 with all enctypes besides DES3, we use + * the original format. For protocol version != 0 or DES3, we + * send down the new style information. + */ + + if (lctx->protocol == 0 && + lctx->rfc1964_kd.ctx_key.type == ENCTYPE_DES_CBC_RAW) + retcode = prepare_krb5_rfc1964_buffer(lctx, buf); + else + retcode = prepare_krb5_ctx_v2_buffer(lctx, buf); + + maj_stat = gss_free_lucid_sec_context(&min_stat, ctx, return_ctx); + if (maj_stat != GSS_S_COMPLETE) { + pgsserr("gss_export_lucid_sec_context", + maj_stat, min_stat, &krb5oid); + printerr(0, "WARN: failed to free lucid sec context\n"); + } + + if (retcode) { + printerr(1, "serialize_krb5_ctx: prepare_krb5_*_buffer " + "failed (retcode = %d)\n", retcode); + goto out_err; + } + + return 0; + +out_err: + printerr(0, "ERROR: failed serializing krb5 context for kernel\n"); + return -1; +} + + +#else /* HAVE_LUCID_CONTEXT_SUPPORT */ + +static int +write_keyblock(char **p, char *end, struct _krb5_keyblock *arg) +{ + gss_buffer_desc tmp; + + if (WRITE_BYTES(p, end, arg->enctype)) return -1; + tmp.length = arg->length; + tmp.value = arg->contents; + if (write_buffer(p, end, &tmp)) return -1; + return 0; +} + +/* + * Function to derive a new key from a given key and given constant data. + */ +static krb5_error_code +derive_key(const krb5_keyblock *in, krb5_keyblock *out, int usage, char extra) +{ + krb5_error_code code; + unsigned char constant_data[K5CLENGTH]; + krb5_data datain; + int keylength; + void *enc; + + /* + * XXX Hack alert. We don't have "legal" access to these + * values and structures located in libk5crypto + */ + switch (in->enctype) { + case ENCTYPE_DES3_CBC_RAW: + keylength = 24; + enc = &krb5int_enc_des3; + break; + case ENCTYPE_ARCFOUR_HMAC: + keylength = 16; + enc = &krb5int_enc_arcfour; + break; + default: + code = KRB5_BAD_ENCTYPE; + goto out; + } + + /* allocate memory for output key */ + if ((out->contents = malloc(keylength)) == NULL) { + code = ENOMEM; + goto out; + } + out->length = keylength; + out->enctype = in->enctype; + + datain.data = (char *) constant_data; + datain.length = K5CLENGTH; + + datain.data[0] = (usage>>24)&0xff; + datain.data[1] = (usage>>16)&0xff; + datain.data[2] = (usage>>8)&0xff; + datain.data[3] = usage&0xff; + + datain.data[4] = (char) extra; + + if ((code = krb5_derive_key(enc, in, out, &datain))) { + free(out->contents); + out->contents = NULL; + } + + out: + if (code) + printerr(0, "ERROR: derive_key returning error %d (%s)\n", + code, error_message(code)); + return (code); +} + +/* + * We really shouldn't know about glue-layer context structure, but + * we need to get at the real krb5 context pointer. This should be + * removed as soon as we say there is no support for MIT Kerberos + * prior to 1.4 -- which gives us "legal" access to the context info. + */ +typedef struct gss_union_ctx_id_t { + gss_OID mech_type; + gss_ctx_id_t internal_ctx_id; +} gss_union_ctx_id_desc, *gss_union_ctx_id_t; + +int +serialize_krb5_ctx(gss_ctx_id_t ctx, gss_buffer_desc *buf) +{ + krb5_gss_ctx_id_t kctx = ((gss_union_ctx_id_t)ctx)->internal_ctx_id; + char *p, *end; + static int constant_zero = 0; + static int constant_one = 1; + static int constant_two = 2; + uint32_t word_seq_send; + u_int64_t seq_send_64bit; + uint32_t v2_flags = 0; + krb5_keyblock derived_key; + uint32_t numkeys; + + if (!(buf->value = calloc(1, MAX_CTX_LEN))) + goto out_err; + p = buf->value; + end = buf->value + MAX_CTX_LEN; + + switch (kctx->sealalg) { + case SEAL_ALG_DES: + /* Versions 0 and 1 */ + if (kctx->initiate) { + if (WRITE_BYTES(&p, end, constant_one)) goto out_err; + } + else { + if (WRITE_BYTES(&p, end, constant_zero)) goto out_err; + } + if (kctx->seed_init) { + if (WRITE_BYTES(&p, end, constant_one)) goto out_err; + } + else { + if (WRITE_BYTES(&p, end, constant_zero)) goto out_err; + } + if (write_bytes(&p, end, &kctx->seed, sizeof(kctx->seed))) + goto out_err; + if (WRITE_BYTES(&p, end, kctx->signalg)) goto out_err; + if (WRITE_BYTES(&p, end, kctx->sealalg)) goto out_err; + if (WRITE_BYTES(&p, end, kctx->endtime)) goto out_err; + word_seq_send = kctx->seq_send; + if (WRITE_BYTES(&p, end, word_seq_send)) goto out_err; + if (write_oid(&p, end, kctx->mech_used)) goto out_err; + + printerr(2, "serialize_krb5_ctx: serializing keys with " + "enctype %d and length %d\n", + kctx->enc->enctype, kctx->enc->length); + + if (write_keyblock(&p, end, kctx->enc)) goto out_err; + if (write_keyblock(&p, end, kctx->seq)) goto out_err; + break; + case SEAL_ALG_MICROSOFT_RC4: + case SEAL_ALG_DES3KD: + /* u32 version; ( 2 ) + * s32 endtime; + * u32 flags; + * #define KRB5_CTX_FLAG_INITIATOR 0x00000001 + * #define KRB5_CTX_FLAG_CFX 0x00000002 + * #define KRB5_CTX_FLAG_ACCEPTOR_SUBKEY 0x00000004 + * u64 seq_send; + * u32 enctype; + * u32 size_of_each_key; ( size in bytes ) + * u32 number_of_keys; ( N (assumed to be 3 for now) ) + * keydata-1; ( Ke (Kenc for DES3) ) + * keydata-2; ( Ki (Kseq for DES3) ) + * keydata-3; ( Kc (derived checksum key) ) + */ + /* Version 2 */ + if (WRITE_BYTES(&p, end , constant_two)) goto out_err; + if (WRITE_BYTES(&p, end, kctx->endtime)) goto out_err; + + /* Only applicable flag for is initiator */ + if (kctx->initiate) v2_flags |= KRB5_CTX_FLAG_INITIATOR; + if (WRITE_BYTES(&p, end, v2_flags)) goto out_err; + + seq_send_64bit = kctx->seq_send; + if (WRITE_BYTES(&p, end, seq_send_64bit)) goto out_err; + + if (WRITE_BYTES(&p, end, kctx->enc->enctype)) goto out_err; + if (WRITE_BYTES(&p, end, kctx->enc->length)) goto out_err; + numkeys = 3; + if (WRITE_BYTES(&p, end, numkeys)) goto out_err; + printerr(2, "serialize_krb5_ctx: serializing %d keys with " + "enctype %d and size %d\n", + numkeys, kctx->enc->enctype, kctx->enc->length); + + /* Ke */ + if (write_bytes(&p, end, kctx->enc->contents, + kctx->enc->length)) + goto out_err; + + /* Ki */ + if (write_bytes(&p, end, kctx->enc->contents, + kctx->enc->length)) + goto out_err; + + /* Kc */ + if (derive_key(kctx->seq, &derived_key, + KG_USAGE_SIGN, KEY_USAGE_SEED_CHECKSUM)) + goto out_err; + if (write_bytes(&p, end, derived_key.contents, + derived_key.length)) + goto out_err; + free(derived_key.contents); + break; + default: + printerr(0, "ERROR: serialize_krb5_ctx: unsupported seal " + "algorithm %d\n", kctx->sealalg); + goto out_err; + } + + buf->length = p - (char *)buf->value; + return 0; + +out_err: + printerr(0, "ERROR: failed serializing krb5 context for kernel\n"); + if (buf->value) { + free(buf->value); + buf->value = NULL; + } + buf->length = 0; + return -1; +} +#endif /* HAVE_LUCID_CONTEXT_SUPPORT */ + +#endif /* HAVE_KRB5 */ diff --git a/lustre/utils/gss/context_spkm3.c b/lustre/utils/gss/context_spkm3.c new file mode 100644 index 0000000..7a77bef --- /dev/null +++ b/lustre/utils/gss/context_spkm3.c @@ -0,0 +1,176 @@ +/* + 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. +*/ + +#include "config.h" +#include +#include +#include +#include +#include "gss_util.h" +#include "gss_oids.h" +#include "err_util.h" +#include "context.h" + +#ifdef HAVE_SPKM3_H + +#include + +/* + * Function: prepare_spkm3_ctx_buffer() + * + * Prepare spkm3 lucid context for the kernel + * + * buf->length should be: + * + * version 4 + * ctx_id 4 + 12 + * qop 4 + * mech_used 4 + 7 + * ret_fl 4 + * req_fl 4 + * share 4 + key_len + * conf_alg 4 + oid_len + * d_conf_key 4 + key_len + * intg_alg 4 + oid_len + * d_intg_key 4 + key_len + * kyestb 4 + oid_len + * owl alg 4 + oid_len +*/ +static int +prepare_spkm3_ctx_buffer(gss_spkm3_lucid_ctx_t *lctx, gss_buffer_desc *buf) +{ + char *p, *end; + unsigned int buf_size = 0; + + buf_size = sizeof(lctx->version) + + lctx->ctx_id.length + sizeof(lctx->ctx_id.length) + + sizeof(lctx->endtime) + + sizeof(lctx->mech_used.length) + lctx->mech_used.length + + sizeof(lctx->ret_flags) + + sizeof(lctx->conf_alg.length) + lctx->conf_alg.length + + sizeof(lctx->derived_conf_key.length) + + lctx->derived_conf_key.length + + sizeof(lctx->intg_alg.length) + lctx->intg_alg.length + + sizeof(lctx->derived_integ_key.length) + + lctx->derived_integ_key.length; + + if (!(buf->value = calloc(1, buf_size))) + goto out_err; + p = buf->value; + end = buf->value + buf_size; + + if (WRITE_BYTES(&p, end, lctx->version)) + goto out_err; + printerr(2, "DEBUG: exporting version = %d\n", lctx->version); + + if (write_buffer(&p, end, &lctx->ctx_id)) + goto out_err; + printerr(2, "DEBUG: exporting ctx_id(%d)\n", lctx->ctx_id.length); + + if (WRITE_BYTES(&p, end, lctx->endtime)) + goto out_err; + printerr(2, "DEBUG: exporting endtime = %d\n", lctx->endtime); + + if (write_buffer(&p, end, &lctx->mech_used)) + goto out_err; + printerr(2, "DEBUG: exporting mech oid (%d)\n", lctx->mech_used.length); + + if (WRITE_BYTES(&p, end, lctx->ret_flags)) + goto out_err; + printerr(2, "DEBUG: exporting ret_flags = %d\n", lctx->ret_flags); + + if (write_buffer(&p, end, &lctx->conf_alg)) + goto out_err; + printerr(2, "DEBUG: exporting conf_alg oid (%d)\n", lctx->conf_alg.length); + + if (write_buffer(&p, end, &lctx->derived_conf_key)) + goto out_err; + printerr(2, "DEBUG: exporting conf key (%d)\n", lctx->derived_conf_key.length); + + if (write_buffer(&p, end, &lctx->intg_alg)) + goto out_err; + printerr(2, "DEBUG: exporting intg_alg oid (%d)\n", lctx->intg_alg.length); + + if (write_buffer(&p, end, &lctx->derived_integ_key)) + goto out_err; + printerr(2, "DEBUG: exporting intg key (%d)\n", lctx->derived_integ_key.length); + + buf->length = p - (char *)buf->value; + return 0; +out_err: + printerr(0, "ERROR: failed serializing spkm3 context for kernel\n"); + if (buf->value) free(buf->value); + buf->length = 0; + + return -1; +} + +/* ANDROS: need to determine which fields of the spkm3_gss_ctx_id_desc_t + * are needed in the kernel for get_mic, validate, wrap, unwrap, and destroy + * and only export those fields to the kernel. + */ +int +serialize_spkm3_ctx(gss_ctx_id_t ctx, gss_buffer_desc *buf) +{ + OM_uint32 vers, ret, maj_stat, min_stat; + void *ret_ctx = 0; + gss_spkm3_lucid_ctx_t *lctx; + + printerr(1, "serialize_spkm3_ctx called\n"); + + printerr(2, "DEBUG: serialize_spkm3_ctx: lucid version!\n"); + maj_stat = gss_export_lucid_sec_context(&min_stat, &ctx, 1, &ret_ctx); + if (maj_stat != GSS_S_COMPLETE) + goto out_err; + + lctx = (gss_spkm3_lucid_ctx_t *)ret_ctx; + + vers = lctx->version; + if (vers != 1) { + printerr(0, "ERROR: unsupported spkm3 context version %d\n", + vers); + goto out_err; + } + ret = prepare_spkm3_ctx_buffer(lctx, buf); + + maj_stat = gss_free_lucid_sec_context(&min_stat, ctx, ret_ctx); + + if (maj_stat != GSS_S_COMPLETE) + printerr(0, "WARN: failed to free lucid sec context\n"); + if (ret) + goto out_err; + printerr(2, "DEBUG: serialize_spkm3_ctx: success\n"); + return 0; + +out_err: + printerr(2, "DEBUG: serialize_spkm3_ctx: failed\n"); + return -1; +} +#endif /* HAVE_SPKM3_H */ diff --git a/lustre/utils/gss/err_util.c b/lustre/utils/gss/err_util.c new file mode 100644 index 0000000..376fb59 --- /dev/null +++ b/lustre/utils/gss/err_util.c @@ -0,0 +1,132 @@ +/* + 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. +*/ + +#include +#include +#include +#include +#include +#include +#include "err_util.h" + +static int verbosity = 0; +static int fg = 0; + +static char message_buf[500]; + +void initerr(char *progname, int set_verbosity, int set_fg) +{ + verbosity = set_verbosity; + fg = set_fg; + if (!fg) + openlog(progname, LOG_PID, LOG_DAEMON); +} + + +void printerr(int priority, char *format, ...) +{ + va_list args; + int ret; + int buf_used, buf_available; + char *buf; + + /* Don't bother formatting a message we're never going to print! */ + if (priority > verbosity) + return; + + buf_used = strlen(message_buf); + /* subtract 4 to leave room for "...\n" if necessary */ + buf_available = sizeof(message_buf) - buf_used - 4; + buf = message_buf + buf_used; + + /* + * Aggregate lines: only print buffer when we get to the + * end of a line or run out of space + */ + va_start(args, format); + ret = vsnprintf(buf, buf_available, format, args); + va_end(args); + + if (ret < 0) + goto printit; + if (ret >= buf_available) { + /* Indicate we're truncating */ + strcat(message_buf, "...\n"); + goto printit; + } + if (message_buf[strlen(message_buf) - 1] == '\n') + goto printit; + return; +printit: + if (fg) { + fprintf(stderr, "%s", message_buf); + } else { + syslog(LOG_ERR, "%s", message_buf); + } + /* reset the buffer */ + memset(message_buf, 0, sizeof(message_buf)); +} + +void print_hexl(int pri, unsigned char *cp, int length) +{ + int i, j, jm; + unsigned char c; + + printerr(pri, "length %d\n",length); + printerr(pri, "\n"); + + for (i = 0; i < length; i += 0x10) { + printerr(pri, " %04x: ", (u_int)i); + jm = length - i; + jm = jm > 16 ? 16 : jm; + + for (j = 0; j < jm; j++) { + if ((j % 2) == 1) + printerr(pri,"%02x ", (u_int)cp[i+j]); + else + printerr(pri,"%02x", (u_int)cp[i+j]); + } + for (; j < 16; j++) { + if ((j % 2) == 1) + printerr(pri," "); + else + printerr(pri," "); + } + printerr(pri," "); + + for (j = 0; j < jm; j++) { + c = cp[i+j]; + c = isprint(c) ? c : '.'; + printerr(pri,"%c", c); + } + printerr(pri,"\n"); + } +} + diff --git a/lustre/utils/gss/err_util.h b/lustre/utils/gss/err_util.h new file mode 100644 index 0000000..1d6b20c --- /dev/null +++ b/lustre/utils/gss/err_util.h @@ -0,0 +1,38 @@ +/* + 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 _ERR_UTIL_H_ +#define _ERR_UTIL_H_ + +void initerr(char *progname, int verbosity, int fg); +void printerr(int priority, char *format, ...); +void print_hexl(int pri, unsigned char *cp, int length); + +#endif /* _ERR_UTIL_H_ */ diff --git a/lustre/utils/gss/gss_oids.c b/lustre/utils/gss/gss_oids.c new file mode 100644 index 0000000..c569b0c --- /dev/null +++ b/lustre/utils/gss/gss_oids.c @@ -0,0 +1,39 @@ +/* + 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. +*/ + +#include +#include + +/* from kerberos source, gssapi_krb5.c */ +gss_OID_desc krb5oid = + {9, "\052\206\110\206\367\022\001\002\002"}; + +gss_OID_desc spkm3oid = + {7, "\053\006\001\005\005\001\003"}; diff --git a/lustre/utils/gss/gss_oids.h b/lustre/utils/gss/gss_oids.h new file mode 100644 index 0000000..8b0a352 --- /dev/null +++ b/lustre/utils/gss/gss_oids.h @@ -0,0 +1,45 @@ +/* + 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 _GSS_OIDS_H_ +#define _GSS_OIDS_H_ + +#include + +extern gss_OID_desc krb5oid; +extern gss_OID_desc spkm3oid; + +#ifndef g_OID_equal +#define g_OID_equal(o1,o2) \ + (((o1)->length == (o2)->length) && \ + (memcmp((o1)->elements,(o2)->elements,(unsigned int) (o1)->length) == 0)) +#endif + +#endif /* _GSS_OIDS_H_ */ diff --git a/lustre/utils/gss/gss_util.c b/lustre/utils/gss/gss_util.c new file mode 100644 index 0000000..275c529 --- /dev/null +++ b/lustre/utils/gss/gss_util.c @@ -0,0 +1,356 @@ +/* + * 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 The Regents of the University of Michigan. + * All rights reserved. + * + * Andy Adamson + * J. Bruce Fields + * Marius Aamodt Eriksen + */ + +/* + * 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. + */ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(HAVE_KRB5) && !defined(GSS_C_NT_HOSTBASED_SERVICE) +#include +#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name +#endif +#include "gss_util.h" +#include "err_util.h" +#include "gssd.h" +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#ifdef HAVE_COM_ERR_H +#include +#endif +#include "lsupport.h" + +/* Global gssd_credentials handle */ +gss_cred_id_t gssd_cred_mds; +gss_cred_id_t gssd_cred_oss; +int gssd_cred_mds_valid = 0; +int gssd_cred_oss_valid = 0; + +char *mds_local_realm = NULL; +char *oss_local_realm = NULL; + +gss_OID g_mechOid = GSS_C_NULL_OID;; + +#if 0 +static void +display_status_1(char *m, u_int32_t code, int type, const gss_OID mech) +{ + u_int32_t maj_stat, min_stat; + gss_buffer_desc msg = GSS_C_EMPTY_BUFFER; + u_int32_t msg_ctx = 0; + char *typestr; + + switch (type) { + case GSS_C_GSS_CODE: + typestr = "GSS"; + break; + case GSS_C_MECH_CODE: + typestr = "mechanism"; + break; + default: + return; + /* NOTREACHED */ + } + + for (;;) { + maj_stat = gss_display_status(&min_stat, code, + type, mech, &msg_ctx, &msg); + if (maj_stat != GSS_S_COMPLETE) { + printerr(0, "ERROR: in call to " + "gss_display_status called from %s\n", m); + break; + } else { + printerr(0, "ERROR: GSS-API: (%s) error in %s(): %s\n", + typestr, m, (char *)msg.value); + } + + if (msg.length != 0) + (void) gss_release_buffer(&min_stat, &msg); + + if (msg_ctx == 0) + break; + } +} +#endif + +static void +display_status_2(char *m, u_int32_t major, u_int32_t minor, const gss_OID mech) +{ + u_int32_t maj_stat1, min_stat1; + u_int32_t maj_stat2, min_stat2; + gss_buffer_desc maj_gss_buf = GSS_C_EMPTY_BUFFER; + gss_buffer_desc min_gss_buf = GSS_C_EMPTY_BUFFER; + char maj_buf[30], min_buf[30]; + char *maj, *min; + u_int32_t msg_ctx = 0; + + /* Get major status message */ + maj_stat1 = gss_display_status(&min_stat1, major, + GSS_C_GSS_CODE, mech, &msg_ctx, &maj_gss_buf); + + if (maj_stat1 != GSS_S_COMPLETE) { + snprintf(maj_buf, sizeof(maj_buf), "(0x%08x)", major); + maj = &maj_buf[0]; + } else { + maj = maj_gss_buf.value; + } + + /* Get minor status message */ + maj_stat2 = gss_display_status(&min_stat2, minor, + GSS_C_MECH_CODE, mech, &msg_ctx, &min_gss_buf); + + if (maj_stat2 != GSS_S_COMPLETE) { + snprintf(min_buf, sizeof(min_buf), "(0x%08x)", minor); + min = &min_buf[0]; + } else { + min = min_gss_buf.value; + } + + printerr(0, "ERROR: GSS-API: error in %s(): %s - %s\n", + m, maj, min); + + if (maj_gss_buf.length != 0) + (void) gss_release_buffer(&min_stat1, &maj_gss_buf); + if (min_gss_buf.length != 0) + (void) gss_release_buffer(&min_stat2, &min_gss_buf); +} + +void +pgsserr(char *msg, u_int32_t maj_stat, u_int32_t min_stat, const gss_OID mech) +{ + display_status_2(msg, maj_stat, min_stat, mech); +} + +static +int extract_realm_name(gss_buffer_desc *name, char **realm) +{ + char *sname, *c; + + sname = malloc(name->length + 1); + if (!sname) { + printerr(0, "out of memory\n"); + return -ENOMEM; + } + + memcpy(sname, name->value, name->length); + sname[name->length] = '\0'; + printerr(1, "service principal: %s\n", sname); + + c = strchr(sname, '@'); + if (!realm) + *realm = NULL; + else { + c++; + *realm = malloc(strlen(c) + 1); + if (!*realm) { + printerr(0, "out of memory\n"); + return -ENOMEM; + } + strcpy(*realm, c); + } + free(sname); + + return 0; +} + +static +int gssd_acquire_cred(char *server_name, gss_cred_id_t *cred, + char **local_realm, int *valid) +{ + gss_buffer_desc name; + gss_name_t target_name; + u_int32_t maj_stat, min_stat; + u_int32_t ignore_maj_stat, ignore_min_stat; + gss_OID name_type; + gss_buffer_desc pbuf; + + *valid = 0; + + name.value = (void *)server_name; + name.length = strlen(server_name); + + maj_stat = gss_import_name(&min_stat, &name, + (const gss_OID) GSS_C_NT_HOSTBASED_SERVICE, + &target_name); + + if (maj_stat != GSS_S_COMPLETE) { + pgsserr("gss_import_name", maj_stat, min_stat, g_mechOid); + return -1; + } + + maj_stat = gss_display_name(&min_stat, target_name, &name, &name_type); + if (maj_stat != GSS_S_COMPLETE) { + pgsserr(0, maj_stat, min_stat, g_mechOid); + return -1; + } + if (extract_realm_name(&name, local_realm)) + return -1; + + maj_stat = gss_acquire_cred(&min_stat, target_name, 0, + GSS_C_NULL_OID_SET, GSS_C_ACCEPT, + cred, NULL, NULL); + + if (maj_stat != GSS_S_COMPLETE) { + pgsserr("gss_acquire_cred", maj_stat, min_stat, g_mechOid); + ignore_maj_stat = gss_display_name(&ignore_min_stat, + target_name, &pbuf, NULL); + if (ignore_maj_stat == GSS_S_COMPLETE) { + printerr(0, "Unable to obtain credentials for '%.*s'\n", + pbuf.length, pbuf.value); + ignore_maj_stat = gss_release_buffer(&ignore_min_stat, + &pbuf); + } + } else + *valid = 1; + + ignore_maj_stat = gss_release_name(&ignore_min_stat, &target_name); + + if (maj_stat != GSS_S_COMPLETE) + return -1; + return 0; +} + +int gssd_prepare_creds(int must_srv_mds, int must_srv_oss) +{ + if (gssd_acquire_cred(GSSD_SERVICE_MDS, &gssd_cred_mds, + &mds_local_realm, &gssd_cred_mds_valid)) { + if (must_srv_mds) + return -1; + } + + if (gssd_acquire_cred(GSSD_SERVICE_OSS, &gssd_cred_oss, + &oss_local_realm, &gssd_cred_oss_valid)) { + if (must_srv_oss) + return -1; + } + + if (!gssd_cred_mds_valid && !gssd_cred_oss_valid) { + printerr(0, "can't obtain both mds & oss creds, exit\n"); + return -1; + } + + printerr(0, "Ready to serve %s\n", + gssd_cred_mds_valid && !gssd_cred_oss_valid ? "Lustre MDS" : + (!gssd_cred_mds_valid && gssd_cred_oss_valid ? "Lustre OSS" : + "Lustre MDS and OSS")); + + return 0; +} + +gss_cred_id_t gssd_select_svc_cred(int lustre_svc) +{ + switch (lustre_svc) { + case LUSTRE_GSS_SVC_MDS: + if (!gssd_cred_mds_valid) { + printerr(0, "ERROR: service cred for mds not ready\n"); + return NULL; + } + printerr(2, "select mds service cred\n"); + return gssd_cred_mds; + case LUSTRE_GSS_SVC_OSS: + if (!gssd_cred_oss_valid) { + printerr(0, "ERROR: service cred for oss not ready\n"); + return NULL; + } + printerr(2, "select oss service cred\n"); + return gssd_cred_oss; + default: + printerr(0, "ERROR: invalid lustre svc id %d\n", lustre_svc); + } + + return NULL; +} + +int gssd_check_mechs(void) +{ + u_int32_t maj_stat, min_stat; + gss_OID_set supported_mechs = GSS_C_NO_OID_SET; + int retval = -1; + + maj_stat = gss_indicate_mechs(&min_stat, &supported_mechs); + if (maj_stat != GSS_S_COMPLETE) { + printerr(0, "Unable to obtain list of supported mechanisms. " + "Check that gss library is properly configured.\n"); + goto out; + } + if (supported_mechs == GSS_C_NO_OID_SET || + supported_mechs->count == 0) { + printerr(0, "Unable to obtain list of supported mechanisms. " + "Check that gss library is properly configured.\n"); + goto out; + } + maj_stat = gss_release_oid_set(&min_stat, &supported_mechs); + retval = 0; +out: + return retval; +} + diff --git a/lustre/utils/gss/gss_util.h b/lustre/utils/gss/gss_util.h new file mode 100644 index 0000000..04caa36 --- /dev/null +++ b/lustre/utils/gss/gss_util.h @@ -0,0 +1,43 @@ +/* + 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 _GSS_UTIL_H_ +#define _GSS_UTIL_H_ + +#include +#include "write_bytes.h" + +extern gss_cred_id_t gssd_creds; + +void pgsserr(char *msg, u_int32_t maj_stat, u_int32_t min_stat, + const gss_OID mech); +int gssd_check_mechs(void); + +#endif /* _GSS_UTIL_H_ */ diff --git a/lustre/utils/gss/gssd.c b/lustre/utils/gss/gssd.c new file mode 100644 index 0000000..137609b --- /dev/null +++ b/lustre/utils/gss/gssd.c @@ -0,0 +1,159 @@ +/* + gssd.c + + Copyright (c) 2000 The Regents of the University of Michigan. + All rights reserved. + + Copyright (c) 2000 Dug Song . + Copyright (c) 2002 Andy Adamson . + Copyright (c) 2002 Marius Aamodt Eriksen . + All rights reserved, all wrongs reversed. + + 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. + +*/ + +#include "config.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include "gssd.h" +#include "err_util.h" +#include "gss_util.h" +#include "krb5_util.h" +#include "lsupport.h" + +char pipefsdir[PATH_MAX] = GSSD_PIPEFS_DIR; +char keytabfile[PATH_MAX] = GSSD_DEFAULT_KEYTAB_FILE; +char ccachedir[PATH_MAX] = GSSD_DEFAULT_CRED_DIR; + +void +sig_die(int signal) +{ + /* destroy krb5 machine creds */ + gssd_destroy_krb5_machine_creds(); + printerr(1, "exiting on signal %d\n", signal); + exit(1); +} + +void +sig_hup(int signal) +{ + /* don't exit on SIGHUP */ + printerr(1, "Received SIGHUP... Ignoring.\n"); + return; +} + +static void +usage(char *progname) +{ + fprintf(stderr, "usage: %s [-f] [-v] [-p pipefsdir] [-k keytab] [-d ccachedir]\n", + progname); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + int fg = 0; + int verbosity = 0; + int opt; + extern char *optarg; + char *progname; + + while ((opt = getopt(argc, argv, "fvrmp:k:d:")) != -1) { + switch (opt) { + case 'f': + fg = 1; + break; + case 'v': + verbosity++; + break; + case 'p': + strncpy(pipefsdir, optarg, sizeof(pipefsdir)); + if (pipefsdir[sizeof(pipefsdir)-1] != '\0') + errx(1, "pipefs path name too long"); + break; + case 'k': + strncpy(keytabfile, optarg, sizeof(keytabfile)); + if (keytabfile[sizeof(keytabfile)-1] != '\0') + errx(1, "keytab path name too long"); + break; + case 'd': + strncpy(ccachedir, optarg, sizeof(ccachedir)); + if (ccachedir[sizeof(ccachedir-1)] != '\0') + errx(1, "ccachedir path name too long"); + break; + default: + usage(argv[0]); + break; + } + } + + if ((progname = strrchr(argv[0], '/'))) + progname++; + else + progname = argv[0]; + + initerr(progname, verbosity, fg); + + if (gssd_check_mechs() != 0) + errx(1, "Problem with gssapi library"); + +#if 0 + /* Determine Kerberos information from the kernel */ + gssd_obtain_kernel_krb5_info(); +#endif + + if (!fg && daemon(0, 0) < 0) + errx(1, "fork"); + + /* This should be checked _after_ daemon(), because we need to own + * the undo-able semaphore by this process + */ + gssd_init_unique(GSSD_CLI); + + /* Process keytab file and get machine credentials. This will modify + * disk status so do it after we are sure we are the only instance + */ + if (gssd_refresh_krb5_machine_creds()) + return -1; + + signal(SIGINT, sig_die); + signal(SIGTERM, sig_die); + signal(SIGHUP, sig_hup); + + lgssd_run(); + printerr(0, "gssd_run returned!\n"); + abort(); +} diff --git a/lustre/utils/gss/gssd.h b/lustre/utils/gss/gssd.h new file mode 100644 index 0000000..0eeef3b --- /dev/null +++ b/lustre/utils/gss/gssd.h @@ -0,0 +1,91 @@ +/* + 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 _RPC_GSSD_H_ +#define _RPC_GSSD_H_ + +#include +#include +#include + +#define MAX_FILE_NAMELEN 32 +#define FD_ALLOC_BLOCK 32 +#ifndef GSSD_PIPEFS_DIR +#define GSSD_PIPEFS_DIR "/var/lib/nfs/rpc_pipefs" +#endif +#define INFO "info" +#define KRB5 "krb5" +#define DNOTIFY_SIGNAL (SIGRTMIN + 3) + +#define GSSD_DEFAULT_CRED_DIR "/tmp" +#define GSSD_DEFAULT_CRED_PREFIX "krb5cc_" +#define GSSD_DEFAULT_MACHINE_CRED_SUFFIX "machine" +#define GSSD_DEFAULT_KEYTAB_FILE "/etc/krb5.keytab" +#define GSSD_SERVICE_MDS "lustre_mds" +#define GSSD_SERVICE_OSS "lustre_oss" +#define GSSD_SERVICE_MDS_NAMELEN 10 +#define GSSD_SERVICE_OSS_NAMELEN 10 + +#define LUSTRE_ROOT_NAME "lustre_root" +#define LUSTRE_ROOT_NAMELEN 11 + +/* + * The gss mechanisms that we can handle + */ +enum {AUTHTYPE_KRB5, AUTHTYPE_SPKM3, AUTHTYPE_LIPKEY}; + + + +extern char pipefsdir[PATH_MAX]; +extern char keytabfile[PATH_MAX]; +extern char ccachedir[PATH_MAX]; +extern char gethostname_ex[PATH_MAX]; + +TAILQ_HEAD(clnt_list_head, clnt_info) clnt_list; + +struct clnt_info { + TAILQ_ENTRY(clnt_info) list; + char *dirname; + int dir_fd; + char *servicename; + int krb5_fd; + int krb5_poll_index; + int spkm3_fd; + int spkm3_poll_index; +}; + +void init_client_list(void); +int update_client_list(void); +void handle_krb5_upcall(struct clnt_info *clp); +void handle_spkm3_upcall(struct clnt_info *clp); +void lgssd_run(void); + + +#endif /* _RPC_GSSD_H_ */ diff --git a/lustre/utils/gss/gssd_main_loop.c b/lustre/utils/gss/gssd_main_loop.c new file mode 100644 index 0000000..e87b4b3 --- /dev/null +++ b/lustre/utils/gss/gssd_main_loop.c @@ -0,0 +1,145 @@ +/* + 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gssd.h" +#include "err_util.h" + +extern struct pollfd *pollarray; +extern int pollsize; + +#define POLL_MILLISECS 500 + +static volatile int dir_changed = 1; + +static void dir_notify_handler(int sig, siginfo_t *si, void *data) +{ + dir_changed = 1; +} + +static void +scan_poll_results(int ret) +{ + int i; + struct clnt_info *clp; + + for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) + { + i = clp->krb5_poll_index; + if (i >= 0 && pollarray[i].revents) { + if (pollarray[i].revents & POLLHUP) + dir_changed = 1; + if (pollarray[i].revents & POLLIN) + handle_krb5_upcall(clp); + pollarray[clp->krb5_poll_index].revents = 0; + ret--; + if (!ret) + break; + } + i = clp->spkm3_poll_index; + if (i >= 0 && pollarray[i].revents) { + if (pollarray[i].revents & POLLHUP) + dir_changed = 1; + if (pollarray[i].revents & POLLIN) + handle_spkm3_upcall(clp); + pollarray[clp->spkm3_poll_index].revents = 0; + ret--; + if (!ret) + break; + } + } +}; + +void +lgssd_run() +{ + int ret; + struct sigaction dn_act; + int fd; + + /* Taken from linux/Documentation/dnotify.txt: */ + dn_act.sa_sigaction = dir_notify_handler; + sigemptyset(&dn_act.sa_mask); + dn_act.sa_flags = SA_SIGINFO; + sigaction(DNOTIFY_SIGNAL, &dn_act, NULL); + + if ((fd = open(pipefsdir, O_RDONLY)) == -1) { + printerr(0, "ERROR: failed to open %s: %s\n", + pipefsdir, strerror(errno)); + exit(1); + } + fcntl(fd, F_SETSIG, DNOTIFY_SIGNAL); + fcntl(fd, F_NOTIFY, DN_CREATE|DN_DELETE|DN_MODIFY|DN_MULTISHOT); + + init_client_list(); + + while (1) { + while (dir_changed) { + printerr(2, "pipefs root dir changed\n"); + dir_changed = 0; + if (update_client_list()) { + printerr(0, "ERROR: couldn't update " + "client list\n"); + exit(1); + } + } + /* race condition here: dir_changed could be set before we + * enter the poll, and we'd never notice if it weren't for the + * timeout. */ + ret = poll(pollarray, pollsize, POLL_MILLISECS); + if (ret < 0) { + if (errno != EINTR) + printerr(0, + "WARNING: error return from poll\n"); + } else if (ret == 0) { + /* timeout */ + } else { /* ret > 0 */ + scan_poll_results(ret); + } + } + close(fd); + return; +} diff --git a/lustre/utils/gss/gssd_proc.c b/lustre/utils/gss/gssd_proc.c new file mode 100644 index 0000000..c88f14c --- /dev/null +++ b/lustre/utils/gss/gssd_proc.c @@ -0,0 +1,1067 @@ +/* + gssd_proc.c + + Copyright (c) 2000-2004 The Regents of the University of Michigan. + All rights reserved. + + Copyright (c) 2000 Dug Song . + Copyright (c) 2001 Andy Adamson . + Copyright (c) 2002 Marius Aamodt Eriksen . + Copyright (c) 2002 Bruce Fields + Copyright (c) 2004 Kevin Coffman + All rights reserved, all wrongs reversed. + + 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gssd.h" +#include "err_util.h" +#include "gss_util.h" +#include "gss_oids.h" +#include "krb5_util.h" +#include "context.h" +#include "lsupport.h" + +/* + * pollarray: + * array of struct pollfd suitable to pass to poll. initialized to + * zero - a zero struct is ignored by poll() because the events mask is 0. + * + * clnt_list: + * linked list of struct clnt_info which associates a clntXXX directory + * with an index into pollarray[], and other basic data about that client. + * + * Directory structure: created by the kernel nfs client + * /pipefsdir/clntXX : one per rpc_clnt struct in the kernel + * /pipefsdir/clntXX/krb5 : read uid for which kernel wants + * a context, write the resulting context + * /pipefsdir/clntXX/info : stores info such as server name + * + * Algorithm: + * Poll all /pipefsdir/clntXX/krb5 files. When ready, data read + * is a uid; performs rpcsec_gss context initialization protocol to + * get a cred for that user. Writes result to corresponding krb5 file + * in a form the kernel code will understand. + * In addition, we make sure we are notified whenever anything is + * created or destroyed in pipefsdir/ or in an of the clntXX directories, + * and rescan the whole pipefsdir when this happens. + */ + +struct pollfd * pollarray; + +int pollsize; /* the size of pollaray (in pollfd's) */ + +static void +destroy_client(struct clnt_info *clp) +{ + if (clp->krb5_poll_index != -1) + memset(&pollarray[clp->krb5_poll_index], 0, + sizeof(struct pollfd)); + if (clp->spkm3_poll_index != -1) + memset(&pollarray[clp->spkm3_poll_index], 0, + sizeof(struct pollfd)); + if (clp->dir_fd != -1) close(clp->dir_fd); + if (clp->krb5_fd != -1) close(clp->krb5_fd); + if (clp->spkm3_fd != -1) close(clp->spkm3_fd); + if (clp->dirname) free(clp->dirname); + if (clp->servicename) free(clp->servicename); + free(clp); +} + +static struct clnt_info * +insert_new_clnt(void) +{ + struct clnt_info *clp = NULL; + + if (!(clp = (struct clnt_info *)calloc(1,sizeof(struct clnt_info)))) { + printerr(0, "ERROR: can't malloc clnt_info: %s\n", + strerror(errno)); + goto out; + } + clp->krb5_poll_index = -1; + clp->spkm3_poll_index = -1; + clp->krb5_fd = -1; + clp->spkm3_fd = -1; + clp->dir_fd = -1; + + TAILQ_INSERT_HEAD(&clnt_list, clp, list); +out: + return clp; +} + +static int +process_clnt_dir_files(struct clnt_info * clp) +{ + char kname[32]; + char sname[32]; + + if (clp->krb5_fd == -1) { + snprintf(kname, sizeof(kname), "%s/krb5", clp->dirname); + clp->krb5_fd = open(kname, O_RDWR); + } + if (clp->spkm3_fd == -1) { + snprintf(sname, sizeof(sname), "%s/spkm3", clp->dirname); + clp->spkm3_fd = open(sname, O_RDWR); + } + if((clp->krb5_fd == -1) && (clp->spkm3_fd == -1)) + return -1; + return 0; +} + +static int +get_poll_index(int *ind) +{ + int i; + + *ind = -1; + for (i=0; ikrb5_fd != -1) && (clp->krb5_poll_index == -1)) { + if (get_poll_index(&clp->krb5_poll_index)) { + printerr(0, "ERROR: Too many krb5 clients\n"); + return -1; + } + pollarray[clp->krb5_poll_index].fd = clp->krb5_fd; + pollarray[clp->krb5_poll_index].events |= POLLIN; + printerr(2, "monitoring krb5 channel under %s\n", + clp->dirname); + } + + if ((clp->spkm3_fd != -1) && (clp->spkm3_poll_index == -1)) { + if (get_poll_index(&clp->spkm3_poll_index)) { + printerr(0, "ERROR: Too many spkm3 clients\n"); + return -1; + } + pollarray[clp->spkm3_poll_index].fd = clp->spkm3_fd; + pollarray[clp->spkm3_poll_index].events |= POLLIN; + } + + return 0; +} + +static void +process_clnt_dir(char *dir) +{ + struct clnt_info * clp; + + if (!(clp = insert_new_clnt())) + goto fail_destroy_client; + + if (!(clp->dirname = calloc(strlen(dir) + 1, 1))) { + goto fail_destroy_client; + } + memcpy(clp->dirname, dir, strlen(dir)); + if ((clp->dir_fd = open(clp->dirname, O_RDONLY)) == -1) { + printerr(0, "ERROR: can't open %s: %s\n", + clp->dirname, strerror(errno)); + goto fail_destroy_client; + } + fcntl(clp->dir_fd, F_SETSIG, DNOTIFY_SIGNAL); + fcntl(clp->dir_fd, F_NOTIFY, DN_CREATE | DN_DELETE | DN_MULTISHOT); + + if (process_clnt_dir_files(clp)) + goto fail_keep_client; + + if (insert_clnt_poll(clp)) + goto fail_destroy_client; + + return; + +fail_destroy_client: + if (clp) { + TAILQ_REMOVE(&clnt_list, clp, list); + destroy_client(clp); + } +fail_keep_client: + /* We couldn't find some subdirectories, but we keep the client + * around in case we get a notification on the directory when the + * subdirectories are created. */ + return; +} + +void +init_client_list(void) +{ + TAILQ_INIT(&clnt_list); + /* Eventually plan to grow/shrink poll array: */ + pollsize = FD_ALLOC_BLOCK; + pollarray = calloc(pollsize, sizeof(struct pollfd)); +} + +/* + * This is run after a DNOTIFY signal, and should clear up any + * directories that are no longer around, and re-scan any existing + * directories, since the DNOTIFY could have been in there. + */ +static void +update_old_clients(struct dirent **namelist, int size) +{ + struct clnt_info *clp; + void *saveprev; + int i, stillhere; + + for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) { + stillhere = 0; + for (i=0; i < size; i++) { + if (!strcmp(clp->dirname, namelist[i]->d_name)) { + stillhere = 1; + break; + } + } + if (!stillhere) { + printerr(2, "destroying client %s\n", clp->dirname); + saveprev = clp->list.tqe_prev; + TAILQ_REMOVE(&clnt_list, clp, list); + destroy_client(clp); + clp = saveprev; + } + } + for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) { + if (!process_clnt_dir_files(clp)) + insert_clnt_poll(clp); + } +} + +/* Search for a client by directory name, return 1 if found, 0 otherwise */ +static int +find_client(char *dirname) +{ + struct clnt_info *clp; + + for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) + if (!strcmp(clp->dirname, dirname)) + return 1; + return 0; +} + +/* Used to read (and re-read) list of clients, set up poll array. */ +int +update_client_list(void) +{ + char lustre_dir[PATH_MAX]; + struct dirent lustre_dirent = { .d_name = "lustre" }; + struct dirent *namelist[1]; + struct stat statbuf; + int i, j; + + if (chdir(pipefsdir) < 0) { + printerr(0, "ERROR: can't chdir to %s: %s\n", + pipefsdir, strerror(errno)); + return -1; + } + + snprintf(lustre_dir, sizeof(lustre_dir), "%s/%s", pipefsdir, "lustre"); + if (stat(lustre_dir, &statbuf) == 0) { + namelist[0] = &lustre_dirent; + j = 1; + printerr(2, "re-processing lustre directory\n"); + } else { + namelist[0] = NULL; + j = 0; + printerr(2, "lustre directory not exist\n"); + } + + update_old_clients(namelist, j); + for (i=0; i < j; i++) { + if (i < FD_ALLOC_BLOCK && + !find_client(namelist[i]->d_name)) + process_clnt_dir(namelist[i]->d_name); + } + + chdir("/"); + return 0; +} + +/* Context creation response. */ +struct lustre_gss_init_res { + gss_buffer_desc gr_ctx; /* context handle */ + u_int gr_major; /* major status */ + u_int gr_minor; /* minor status */ + u_int gr_win; /* sequence window */ + gss_buffer_desc gr_token; /* token */ +}; + +struct lustre_gss_data { + int lgd_established; + int lgd_lustre_svc; /* mds/oss */ + int lgd_uid; /* uid */ + char *lgd_uuid; /* client device uuid */ + gss_name_t lgd_name; /* service name */ + + gss_OID lgd_mech; /* mech OID */ + u_int lgd_req_flags; /* request flags */ + gss_cred_id_t lgd_cred; /* credential */ + gss_ctx_id_t lgd_ctx; /* session context */ + gss_buffer_desc lgd_rmt_ctx; /* remote handle of context */ + uint32_t lgd_seq_win; /* sequence window */ + + int lgd_rpc_err; + int lgd_gss_err; +}; + +static int +do_downcall(int k5_fd, struct lgssd_upcall_data *updata, + struct lustre_gss_data *lgd, gss_buffer_desc *context_token) +{ + char *buf = NULL, *p = NULL, *end = NULL; + unsigned int timeout = 0; /* XXX decide on a reasonable value */ + unsigned int buf_size = 0; + + printerr(2, "doing downcall\n"); + buf_size = sizeof(updata->seq) + sizeof(timeout) + + sizeof(lgd->lgd_seq_win) + + sizeof(lgd->lgd_rmt_ctx.length) + lgd->lgd_rmt_ctx.length + + sizeof(context_token->length) + context_token->length; + p = buf = malloc(buf_size); + end = buf + buf_size; + + if (WRITE_BYTES(&p, end, updata->seq)) goto out_err; + /* Not setting any timeout for now: */ + if (WRITE_BYTES(&p, end, timeout)) goto out_err; + if (WRITE_BYTES(&p, end, lgd->lgd_seq_win)) goto out_err; + if (write_buffer(&p, end, &lgd->lgd_rmt_ctx)) goto out_err; + if (write_buffer(&p, end, context_token)) goto out_err; + + if (write(k5_fd, buf, p - buf) < p - buf) goto out_err; + if (buf) free(buf); + return 0; +out_err: + if (buf) free(buf); + printerr(0, "ERROR: Failed to write downcall!\n"); + return -1; +} + +static int +do_error_downcall(int k5_fd, struct lgssd_upcall_data *updata, + int rpc_err, int gss_err) +{ + char buf[1024]; + char *p = buf, *end = buf + 1024; + unsigned int timeout = 0; + int zero = 0; + + printerr(1, "doing error downcall\n"); + + if (WRITE_BYTES(&p, end, updata->seq)) goto out_err; + if (WRITE_BYTES(&p, end, timeout)) goto out_err; + /* use seq_win = 0 to indicate an error: */ + if (WRITE_BYTES(&p, end, zero)) goto out_err; + if (WRITE_BYTES(&p, end, rpc_err)) goto out_err; + if (WRITE_BYTES(&p, end, gss_err)) goto out_err; + + if (write(k5_fd, buf, p - buf) < p - buf) goto out_err; + return 0; +out_err: + printerr(0, "Failed to write error downcall!\n"); + return -1; +} + +#if 0 +/* + * Create an RPC connection and establish an authenticated + * gss context with a server. + */ +int create_auth_rpc_client(struct clnt_info *clp, + CLIENT **clnt_return, + AUTH **auth_return, + uid_t uid, + int authtype) +{ + CLIENT *rpc_clnt = NULL; + struct rpc_gss_sec sec; + AUTH *auth = NULL; + uid_t save_uid = -1; + int retval = -1; + int errcode; + OM_uint32 min_stat; + char rpc_errmsg[1024]; + int sockp = RPC_ANYSOCK; + int sendsz = 32768, recvsz = 32768; + struct addrinfo ai_hints, *a = NULL; + char service[64]; + char *at_sign; + + /* Create the context as the user (not as root) */ + save_uid = geteuid(); + if (setfsuid(uid) != 0) { + printerr(0, "WARNING: Failed to setfsuid for " + "user with uid %d\n", uid); + goto out_fail; + } + printerr(2, "creating context using fsuid %d (save_uid %d)\n", + uid, save_uid); + + sec.qop = GSS_C_QOP_DEFAULT; + sec.svc = RPCSEC_GSS_SVC_NONE; + sec.cred = GSS_C_NO_CREDENTIAL; + sec.req_flags = 0; + if (authtype == AUTHTYPE_KRB5) { + sec.mech = (gss_OID)&krb5oid; + sec.req_flags = GSS_C_MUTUAL_FLAG; + } + else if (authtype == AUTHTYPE_SPKM3) { + sec.mech = (gss_OID)&spkm3oid; + /* XXX sec.req_flags = GSS_C_ANON_FLAG; + * Need a way to switch.... + */ + sec.req_flags = GSS_C_MUTUAL_FLAG; + } + else { + printerr(0, "ERROR: Invalid authentication type (%d) " + "in create_auth_rpc_client\n", authtype); + goto out_fail; + } + + + if (authtype == AUTHTYPE_KRB5) { +#ifdef HAVE_SET_ALLOWABLE_ENCTYPES + /* + * Do this before creating rpc connection since we won't need + * rpc connection if it fails! + */ + if (limit_krb5_enctypes(&sec, uid)) { + printerr(1, "WARNING: Failed while limiting krb5 " + "encryption types for user with uid %d\n", + uid); + goto out_fail; + } +#endif + } + + /* create an rpc connection to the nfs server */ + + printerr(2, "creating %s client for server %s\n", clp->protocol, + clp->servername); + + memset(&ai_hints, '\0', sizeof(ai_hints)); + ai_hints.ai_family = PF_INET; + ai_hints.ai_flags |= AI_CANONNAME; + if ((strcmp(clp->protocol, "tcp")) == 0) { + ai_hints.ai_socktype = SOCK_STREAM; + ai_hints.ai_protocol = IPPROTO_TCP; + } else if ((strcmp(clp->protocol, "udp")) == 0) { + ai_hints.ai_socktype = SOCK_DGRAM; + ai_hints.ai_protocol = IPPROTO_UDP; + } else { + printerr(0, "WARNING: unrecognized protocol, '%s', requested " + "for connection to server %s for user with uid %d", + clp->protocol, clp->servername, uid); + goto out_fail; + } + + /* extract the service name from clp->servicename */ + if ((at_sign = strchr(clp->servicename, '@')) == NULL) { + printerr(0, "WARNING: servicename (%s) not formatted as " + "expected with service@host", clp->servicename); + goto out_fail; + } + if ((at_sign - clp->servicename) >= sizeof(service)) { + printerr(0, "WARNING: service portion of servicename (%s) " + "is too long!", clp->servicename); + goto out_fail; + } + strncpy(service, clp->servicename, at_sign - clp->servicename); + service[at_sign - clp->servicename] = '\0'; + + errcode = getaddrinfo(clp->servername, service, &ai_hints, &a); + if (errcode) { + printerr(0, "WARNING: Error from getaddrinfo for server " + "'%s': %s", clp->servername, gai_strerror(errcode)); + goto out_fail; + } + + if (a == NULL) { + printerr(0, "WARNING: No address information found for " + "connection to server %s for user with uid %d", + clp->servername, uid); + goto out_fail; + } + if (a->ai_protocol == IPPROTO_TCP) { + if ((rpc_clnt = clnttcp_create( + (struct sockaddr_in *) a->ai_addr, + clp->prog, clp->vers, &sockp, + sendsz, recvsz)) == NULL) { + snprintf(rpc_errmsg, sizeof(rpc_errmsg), + "WARNING: can't create tcp rpc_clnt " + "for server %s for user with uid %d", + clp->servername, uid); + printerr(0, "%s\n", + clnt_spcreateerror(rpc_errmsg)); + goto out_fail; + } + } else if (a->ai_protocol == IPPROTO_UDP) { + const struct timeval timeout = {5, 0}; + if ((rpc_clnt = clntudp_bufcreate( + (struct sockaddr_in *) a->ai_addr, + clp->prog, clp->vers, timeout, + &sockp, sendsz, recvsz)) == NULL) { + snprintf(rpc_errmsg, sizeof(rpc_errmsg), + "WARNING: can't create udp rpc_clnt " + "for server %s for user with uid %d", + clp->servername, uid); + printerr(0, "%s\n", + clnt_spcreateerror(rpc_errmsg)); + goto out_fail; + } + } else { + /* Shouldn't happen! */ + printerr(0, "ERROR: requested protocol '%s', but " + "got addrinfo with protocol %d", + clp->protocol, a->ai_protocol); + goto out_fail; + } + /* We're done with this */ + freeaddrinfo(a); + a = NULL; + + printerr(2, "creating context with server %s\n", clp->servicename); + auth = authgss_create_default(rpc_clnt, clp->servicename, &sec); + if (!auth) { + /* Our caller should print appropriate message */ + printerr(2, "WARNING: Failed to create %s context for " + "user with uid %d for server %s\n", + (authtype == AUTHTYPE_KRB5 ? "krb5":"spkm3"), + uid, clp->servername); + goto out_fail; + } + + /* Success !!! */ + rpc_clnt->cl_auth = auth; + *clnt_return = rpc_clnt; + *auth_return = auth; + retval = 0; + + out: + if (sec.cred != GSS_C_NO_CREDENTIAL) + gss_release_cred(&min_stat, &sec.cred); + if (a != NULL) freeaddrinfo(a); + /* Restore euid to original value */ + if ((save_uid != -1) && (setfsuid(save_uid) != uid)) { + printerr(0, "WARNING: Failed to restore fsuid" + " to uid %d from %d\n", save_uid, uid); + } + return retval; + + out_fail: + /* Only destroy here if failure. Otherwise, caller is responsible */ + if (rpc_clnt) clnt_destroy(rpc_clnt); + + goto out; +} +#endif + +static +int do_negotiation(struct lustre_gss_data *lgd, + gss_buffer_desc *gss_token, + struct lustre_gss_init_res *gr, + int timeout) +{ + char *file = "/proc/fs/lustre/gss/init_channel"; + struct lgssd_ioctl_param param; + struct passwd *pw; + int fd, ret; + char outbuf[8192]; + unsigned int *p; + int res; + + pw = getpwuid(lgd->lgd_uid); + if (!pw) { + printerr(0, "no uid %u in local user database\n", + lgd->lgd_uid); + return -1; + } + + param.version = GSSD_INTERFACE_VERSION; + param.uuid = lgd->lgd_uuid; + param.lustre_svc = lgd->lgd_lustre_svc; + param.uid = lgd->lgd_uid; + param.gid = pw->pw_gid; + param.send_token_size = gss_token->length; + param.send_token = (char *) gss_token->value; + param.reply_buf_size = sizeof(outbuf); + param.reply_buf = outbuf; + + fd = open(file, O_RDWR); + if (fd < 0) { + printerr(0, "can't open file %s\n", file); + return -1; + } + + ret = write(fd, ¶m, sizeof(param)); + + if (ret != sizeof(param)) { + printerr(0, "lustre ioctl err: %d\n", strerror(errno)); + close(fd); + return -1; + } + if (param.status) { + close(fd); + printerr(0, "status: %d (%s)\n", + param.status, strerror((int)param.status)); + if (param.status == -ETIMEDOUT) { + /* kernel return -ETIMEDOUT means the rpc timedout, + * we should notify the caller to reinitiate the + * gss negotiation, by return -ERESTART + */ + lgd->lgd_rpc_err = -ERESTART; + lgd->lgd_gss_err = 0; + } else { + lgd->lgd_rpc_err = param.status; + lgd->lgd_gss_err = 0; + } + return -1; + } + p = (unsigned int *)outbuf; + res = *p++; + gr->gr_major = *p++; + gr->gr_minor = *p++; + gr->gr_win = *p++; + + gr->gr_ctx.length = *p++; + gr->gr_ctx.value = malloc(gr->gr_ctx.length); + memcpy(gr->gr_ctx.value, p, gr->gr_ctx.length); + p += (((gr->gr_ctx.length + 3) & ~3) / 4); + + gr->gr_token.length = *p++; + gr->gr_token.value = malloc(gr->gr_token.length); + memcpy(gr->gr_token.value, p, gr->gr_token.length); + p += (((gr->gr_token.length + 3) & ~3) / 4); + + printerr(2, "do_negotiation: receive handle len %d, token len %d\n", + gr->gr_ctx.length, gr->gr_token.length); + close(fd); + return 0; +} + +static +int gssd_refresh_lgd(struct lustre_gss_data *lgd) +{ + struct lustre_gss_init_res gr; + gss_buffer_desc *recv_tokenp, send_token; + OM_uint32 maj_stat, min_stat, call_stat, ret_flags; + + /* GSS context establishment loop. */ + memset(&gr, 0, sizeof(gr)); + recv_tokenp = GSS_C_NO_BUFFER; + + for (;;) { + /* print the token we just received */ + if (recv_tokenp != GSS_C_NO_BUFFER) { + printerr(3, "The received token length %d\n", + recv_tokenp->length); + print_hexl(3, recv_tokenp->value, recv_tokenp->length); + } + + maj_stat = gss_init_sec_context(&min_stat, + lgd->lgd_cred, + &lgd->lgd_ctx, + lgd->lgd_name, + lgd->lgd_mech, + lgd->lgd_req_flags, + 0, /* time req */ + NULL, /* channel */ + recv_tokenp, + NULL, /* used mech */ + &send_token, + &ret_flags, + NULL); /* time rec */ + + if (recv_tokenp != GSS_C_NO_BUFFER) { + gss_release_buffer(&min_stat, &gr.gr_token); + recv_tokenp = GSS_C_NO_BUFFER; + } + if (maj_stat != GSS_S_COMPLETE && + maj_stat != GSS_S_CONTINUE_NEEDED) { + pgsserr("gss_init_sec_context", maj_stat, min_stat, + lgd->lgd_mech); + break; + } + if (send_token.length != 0) { + memset(&gr, 0, sizeof(gr)); + + /* print the token we are about to send */ + printerr(3, "token being sent length %d\n", + send_token.length); + print_hexl(3, send_token.value, send_token.length); + + call_stat = do_negotiation(lgd, &send_token, &gr, 0); + gss_release_buffer(&min_stat, &send_token); + + if (call_stat != 0 || + (gr.gr_major != GSS_S_COMPLETE && + gr.gr_major != GSS_S_CONTINUE_NEEDED)) { + printerr(0, "call stat %d, major stat 0x%x\n", + (int)call_stat, gr.gr_major); + return -1; + } + + if (gr.gr_ctx.length != 0) { + if (lgd->lgd_rmt_ctx.value) + gss_release_buffer(&min_stat, + &lgd->lgd_rmt_ctx); + lgd->lgd_rmt_ctx = gr.gr_ctx; + } + if (gr.gr_token.length != 0) { + if (maj_stat != GSS_S_CONTINUE_NEEDED) + break; + recv_tokenp = &gr.gr_token; + } + } + + /* GSS_S_COMPLETE => check gss header verifier, + * usually checked in gss_validate + */ + if (maj_stat == GSS_S_COMPLETE) { + lgd->lgd_established = 1; + lgd->lgd_seq_win = gr.gr_win; + break; + } + } + /* End context negotiation loop. */ + if (!lgd->lgd_established) { + if (gr.gr_token.length != 0) + gss_release_buffer(&min_stat, &gr.gr_token); + + printerr(0, "context negotiation failed\n"); + return -1; + } + + printerr(2, "successfully refreshed lgd\n"); + return 0; +} + +static +int gssd_create_lgd(struct clnt_info *clp, + struct lustre_gss_data *lgd, + struct lgssd_upcall_data *updata, + int authtype) +{ + gss_buffer_desc sname; + OM_uint32 maj_stat, min_stat; + int retval = -1; + + lgd->lgd_established = 0; + lgd->lgd_lustre_svc = updata->svc; + lgd->lgd_uid = updata->uid; + lgd->lgd_uuid = updata->obd; + + switch (authtype) { + case AUTHTYPE_KRB5: + lgd->lgd_mech = (gss_OID) &krb5oid; + lgd->lgd_req_flags = GSS_C_MUTUAL_FLAG; + break; + case AUTHTYPE_SPKM3: + lgd->lgd_mech = (gss_OID) &spkm3oid; + /* XXX sec.req_flags = GSS_C_ANON_FLAG; + * Need a way to switch.... + */ + lgd->lgd_req_flags = GSS_C_MUTUAL_FLAG; + break; + default: + printerr(0, "Invalid authentication type (%d)\n", authtype); + return -1; + } + + lgd->lgd_cred = GSS_C_NO_CREDENTIAL; + lgd->lgd_ctx = GSS_C_NO_CONTEXT; + lgd->lgd_rmt_ctx = (gss_buffer_desc) GSS_C_EMPTY_BUFFER; + lgd->lgd_seq_win = 0; + + sname.value = clp->servicename; + sname.length = strlen(clp->servicename); + + maj_stat = gss_import_name(&min_stat, &sname, + (gss_OID) GSS_C_NT_HOSTBASED_SERVICE, + &lgd->lgd_name); + if (maj_stat != GSS_S_COMPLETE) { + pgsserr(0, maj_stat, min_stat, lgd->lgd_mech); + goto out_fail; + } + + retval = gssd_refresh_lgd(lgd); + + if (lgd->lgd_name != GSS_C_NO_NAME) + gss_release_name(&min_stat, &lgd->lgd_name); + + if (lgd->lgd_cred != GSS_C_NO_CREDENTIAL) + gss_release_cred(&min_stat, &lgd->lgd_cred); + + out_fail: + return retval; +} + +static +void gssd_free_lgd(struct lustre_gss_data *lgd) +{ + gss_buffer_t token = GSS_C_NO_BUFFER; + OM_uint32 maj_stat, min_stat; + + if (lgd->lgd_ctx == GSS_C_NO_CONTEXT) + return; + + maj_stat = gss_delete_sec_context(&min_stat, &lgd->lgd_ctx, token); +} + +static +int construct_service_name(struct clnt_info *clp, + struct lgssd_upcall_data *ud) +{ + const int buflen = 256; + char name[buflen]; + + if (clp->servicename) { + free(clp->servicename); + clp->servicename = NULL; + } + + if (ptl_nid2hostname(ud->nid, name, buflen)) + return -1; + + clp->servicename = malloc(32 + strlen(name)); + if (!clp->servicename) { + printerr(0, "can't alloc memory\n"); + return -1; + } + sprintf(clp->servicename, "%s@%s", + ud->svc == LUSTRE_GSS_SVC_MDS ? + GSSD_SERVICE_MDS : GSSD_SERVICE_OSS, + name); + printerr(2, "constructed servicename: %s\n", clp->servicename); + return 0; +} + +/* + * this code uses the userland rpcsec gss library to create a krb5 + * context on behalf of the kernel + */ +void +handle_krb5_upcall(struct clnt_info *clp) +{ + gss_buffer_desc token; + struct lgssd_upcall_data updata; + struct lustre_gss_data lgd; + char **credlist = NULL; + char **ccname; + + printerr(2, "handling krb5 upcall\n"); + + lgd.lgd_rpc_err = -EPERM; /* default error code */ + + token.length = 0; + token.value = NULL; + + if (read(clp->krb5_fd, &updata, sizeof(updata)) != sizeof(updata)) { + printerr(0, "WARNING: failed reading from krb5 " + "upcall pipe: %s\n", strerror(errno)); + goto out; + } + + printerr(1, "krb5 upcall: seq %u, uid %u, svc %u, nid 0x%llx, obd %s\n", + updata.seq, updata.uid, updata.svc, updata.nid, updata.obd); + + if (updata.svc != LUSTRE_GSS_SVC_MDS && + updata.svc != LUSTRE_GSS_SVC_OSS) { + printerr(0, "invalid svc %d\n", updata.svc); + lgd.lgd_rpc_err = -EPROTO; + goto out_return_error; + } + updata.obd[sizeof(updata.obd)-1] = '\0'; + + if (construct_service_name(clp, &updata)) { + printerr(0, "failed to construct service name\n"); + goto out_return_error; + } + + if (updata.uid == 0) { + int success = 0; + + /* + * Get a list of credential cache names and try each + * of them until one works or we've tried them all + */ + if (gssd_get_krb5_machine_cred_list(&credlist)) { + printerr(0, "ERROR: Failed to obtain machine " + "credentials for %s\n", clp->servicename); + goto out_return_error; + } + for (ccname = credlist; ccname && *ccname; ccname++) { + gssd_setup_krb5_machine_gss_ccache(*ccname); + if ((gssd_create_lgd(clp, &lgd, &updata, + AUTHTYPE_KRB5)) == 0) { + /* Success! */ + success++; + break; + } + printerr(2, "WARNING: Failed to create krb5 context " + "for user with uid %d with credentials " + "cache %s for service %s\n", + updata.uid, *ccname, clp->servicename); + } + gssd_free_krb5_machine_cred_list(credlist); + if (!success) { + printerr(0, "ERROR: Failed to create krb5 context " + "for user with uid %d with any " + "credentials cache for service %s\n", + updata.uid, clp->servicename); + goto out_return_error; + } + } + else { + /* Tell krb5 gss which credentials cache to use */ + gssd_setup_krb5_user_gss_ccache(updata.uid, clp->servicename); + + if ((gssd_create_lgd(clp, &lgd, &updata, AUTHTYPE_KRB5)) != 0) { + printerr(0, "WARNING: Failed to create krb5 context " + "for user with uid %d for service %s\n", + updata.uid, clp->servicename); + goto out_return_error; + } + } + + if (serialize_context_for_kernel(lgd.lgd_ctx, &token, &krb5oid)) { + printerr(0, "WARNING: Failed to serialize krb5 context for " + "user with uid %d for service %s\n", + updata.uid, clp->servicename); + goto out_return_error; + } + + printerr(1, "refreshed: %u@%s for %s\n", + updata.uid, updata.obd, clp->servicename); + do_downcall(clp->krb5_fd, &updata, &lgd, &token); + +out: + if (token.value) + free(token.value); + + gssd_free_lgd(&lgd); + return; + +out_return_error: + do_error_downcall(clp->krb5_fd, &updata, + lgd.lgd_rpc_err, lgd.lgd_gss_err); + goto out; +} + +/* + * this code uses the userland rpcsec gss library to create an spkm3 + * context on behalf of the kernel + */ +void +handle_spkm3_upcall(struct clnt_info *clp) +{ +#if 0 + uid_t uid; + CLIENT *rpc_clnt = NULL; + AUTH *auth = NULL; + struct authgss_private_data pd; + gss_buffer_desc token; + + printerr(2, "handling spkm3 upcall\n"); + + token.length = 0; + token.value = NULL; + + if (read(clp->spkm3_fd, &uid, sizeof(uid)) < sizeof(uid)) { + printerr(0, "WARNING: failed reading uid from spkm3 " + "upcall pipe: %s\n", strerror(errno)); + goto out; + } + + if (create_auth_rpc_client(clp, &rpc_clnt, &auth, uid, AUTHTYPE_SPKM3)) { + printerr(0, "WARNING: Failed to create spkm3 context for " + "user with uid %d\n", uid); + goto out_return_error; + } + + if (!authgss_get_private_data(auth, &pd)) { + printerr(0, "WARNING: Failed to obtain authentication " + "data for user with uid %d for server %s\n", + uid, clp->servername); + goto out_return_error; + } + + if (serialize_context_for_kernel(pd.pd_ctx, &token, &spkm3oid)) { + printerr(0, "WARNING: Failed to serialize spkm3 context for " + "user with uid %d for server\n", + uid, clp->servername); + goto out_return_error; + } + + do_downcall(clp->spkm3_fd, uid, &pd, &token); + +out: + if (token.value) + free(token.value); + if (auth) + AUTH_DESTROY(auth); + if (rpc_clnt) + clnt_destroy(rpc_clnt); + return; + +out_return_error: + do_error_downcall(clp->spkm3_fd, uid, -1); + goto out; +#endif +} diff --git a/lustre/utils/gss/krb5_util.c b/lustre/utils/gss/krb5_util.c new file mode 100644 index 0000000..73477b9 --- /dev/null +++ b/lustre/utils/gss/krb5_util.c @@ -0,0 +1,1121 @@ +/* + * 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 + * J. Bruce Fields + * Marius Aamodt Eriksen + * Kevin Coffman + */ + +/* + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef USE_PRIVATE_KRB5_FUNCTIONS +#include +#endif +#include + +#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; + +/* realm of this node */ +char *this_realm = 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; + + *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); + + 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); +#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 : "", kt_name); + if (pname) KRB5_FREE_UNPARSED_NAME(context, pname); + goto out; + } + + /* + * Initialize cache file which we're going to be using + */ + + snprintf(cc_name, sizeof(cc_name), "FILE:%s/%s%s_%s", + 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; + } + + if (this_realm == NULL) { + code = krb5_get_default_realm(context, &this_realm); + if (code) { + printerr(0, "ERROR: get default realm: %s\n", + error_message(code)); + retval = code; + goto out; + } + printerr(1, "Local realm: %s\n", this_realm); + } + + 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/@ on MDT nodes, " + "and %s@ 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]; + char extrainfo[1024]; + 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", pipefsdir, "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; + } + if ((nbytes = read(fd, buf, sizeof(buf))) == -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; + goto do_the_parse; + } + numfields = sscanf(buf, "enctypes: %s\n%s", enctypes, extrainfo); + 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 (numfields > 1) { + printerr(0, "WARNING: gssd_obtain_kernel_krb5_info: " + "Extra information, '%s', from '%s' is ignored\n", + enctype_file_name, extrainfo); + use_default_enctypes = 1; + 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); + } +} diff --git a/lustre/utils/gss/krb5_util.h b/lustre/utils/gss/krb5_util.h new file mode 100644 index 0000000..cf574df --- /dev/null +++ b/lustre/utils/gss/krb5_util.h @@ -0,0 +1,58 @@ +#ifndef KRB5_UTIL_H +#define KRB5_UTIL_H + +#include + +/* + * List of principals from our keytab that we + * may try to get credentials for + */ +struct gssd_k5_kt_princ { + struct gssd_k5_kt_princ *next; + krb5_principal princ; + unsigned int fl_root:1, + fl_mds:1; + char *ccname; + char *realm; + krb5_timestamp endtime; +}; + + +void gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername); +int gssd_get_krb5_machine_cred_list(char ***list); +int gssd_refresh_krb5_machine_creds(void); +void gssd_free_krb5_machine_cred_list(char **list); +void gssd_setup_krb5_machine_gss_ccache(char *servername); +void gssd_destroy_krb5_machine_creds(void); +void gssd_obtain_kernel_krb5_info(void); + + +#endif /* KRB5_UTIL_H */ +#ifndef KRB5_UTIL_H +#define KRB5_UTIL_H + +#include + +/* + * List of principals from our keytab that we + * may try to get credentials for + */ +struct gssd_k5_kt_princ { + struct gssd_k5_kt_princ *next; + krb5_principal princ; + char *ccname; + char *realm; + krb5_timestamp endtime; +}; + + +void gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername); +int gssd_get_krb5_machine_cred_list(char ***list); +int gssd_refresh_krb5_machine_creds(void); +void gssd_free_krb5_machine_cred_list(char **list); +void gssd_setup_krb5_machine_gss_ccache(char *servername); +void gssd_destroy_krb5_machine_creds(void); +void gssd_obtain_kernel_krb5_info(void); + + +#endif /* KRB5_UTIL_H */ diff --git a/lustre/utils/gss/lsupport.c b/lustre/utils/gss/lsupport.c new file mode 100644 index 0000000..e6d86bf --- /dev/null +++ b/lustre/utils/gss/lsupport.c @@ -0,0 +1,604 @@ +/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*- + * vim:expandtab:shiftwidth=8:tabstop=8: + * + * Copyright (c) 2005 Cluster File Systems, Inc. + * + * This file is part of Lustre, http://www.lustre.org. + * + * Lustre is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * Lustre is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Lustre; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "err_util.h" +#include "gssd.h" +#include "lsupport.h" + +/**************************************** + * exclusive startup * + ****************************************/ + +static struct __sem_s { + char *name; + key_t sem_key; + int sem_id; +} sems[2] = { + [GSSD_CLI] = { "client", 0x3a92d473, 0 }, + [GSSD_SVC] = { "server", 0x3b92d473, 0 }, +}; + +void gssd_init_unique(int type) +{ + struct __sem_s *sem = &sems[type]; + struct sembuf sembuf; + + assert(type == GSSD_CLI || type == GSSD_SVC); + +again: + sem->sem_id = semget(sem->sem_key, 1, IPC_CREAT | IPC_EXCL | 0700); + if (sem->sem_id == -1) { + if (errno != EEXIST) { + printerr(0, "Create sem: %s\n", strerror(errno)); + exit(-1); + } + + /* already exist. Note there's still a small window racing + * with other processes, due to the stupid semaphore semantics. + */ + sem->sem_id = semget(sem->sem_key, 0, 0700); + if (sem->sem_id == -1) { + if (errno == ENOENT) { + printerr(0, "another instance just exit, " + "try again\n"); + goto again; + } + + printerr(0, "Obtain sem: %s\n", strerror(errno)); + exit(-1); + } + } else { + int val = 1; + + if (semctl(sem->sem_id, 0, SETVAL, val) == -1) { + printerr(0, "Initialize sem: %s\n", + strerror(errno)); + exit(-1); + } + } + + sembuf.sem_num = 0; + sembuf.sem_op = -1; + sembuf.sem_flg = IPC_NOWAIT | SEM_UNDO; + + if (semop(sem->sem_id, &sembuf, 1) != 0) { + if (errno == EAGAIN) { + printerr(0, "Another instance is running, exit\n"); + exit(0); + } + printerr(0, "Grab sem: %s\n", strerror(errno)); + exit(0); + } + + printerr(2, "Successfully created %s global identity\n", sem->name); +} + +void gssd_exit_unique(int type) +{ + assert(type == GSSD_CLI || type == GSSD_SVC); + + /* + * do nothing. we can't remove the sem here, otherwise the race + * window would be much bigger. So it's sad we have to leave the + * sem in the system forever. + */ +} + +/**************************************** + * client side resolvation: * + * nal/netid/nid => hostname * + ****************************************/ + +char gethostname_ex[PATH_MAX] = GSSD_DEFAULT_GETHOSTNAME_EX; + +typedef int ptl_nid2hostname_t(char *nal, uint32_t net, uint32_t addr, + char *buf, int buflen); + +/* FIXME what about IPv6? */ +static +int socknal_nid2hostname(char *nal, uint32_t net, uint32_t addr, + char *buf, int buflen) +{ + struct hostent *ent; + + addr = htonl(addr); + ent = gethostbyaddr(&addr, sizeof(addr), AF_INET); + if (!ent) { + printerr(0, "%s: can't resolve 0x%x\n", nal, addr); + return -1; + } + if (strlen(ent->h_name) >= buflen) { + printerr(0, "%s: name too long: %s\n", nal, ent->h_name); + return -1; + } + strcpy(buf, ent->h_name); + + printerr(2, "%s: net 0x%x, addr 0x%x => %s\n", + nal, net, addr, buf); + return 0; +} + +static +int lonal_nid2hostname(char *nal, uint32_t net, uint32_t addr, + char *buf, int buflen) +{ + struct utsname uts; + struct hostent *ent; + + if (addr) { + printerr(0, "%s: addr is 0x%x, we expect 0\n", nal, addr); + return -1; + } + + if (uname(&uts)) { + printerr(0, "%s: failed obtain local machine name\n", nal); + return -1; + } + + ent = gethostbyname(uts.nodename); + if (!ent) { + printerr(0, "%s: failed obtain canonical name of %s\n", + nal, uts.nodename); + return -1; + } + + if (strlen(ent->h_name) >= buflen) { + printerr(0, "%s: name too long: %s\n", nal, ent->h_name); + return -1; + } + strcpy(buf, ent->h_name); + + printerr(2, "%s: addr 0x%x => %s\n", nal, addr, buf); + return 0; +} + +static int is_space(char c) +{ + return (c == ' ' || c == '\t' || c == '\n'); +} + +static +int external_nid2hostname(char *nal, uint32_t net, uint32_t addr, + char *namebuf, int namebuflen) +{ + const int bufsize = PATH_MAX + 256; + char buf[bufsize], *head, *tail; + FILE *fghn; + + sprintf(buf, "%s %s 0x%x 0x%x", gethostname_ex, nal, net, addr); + printerr(2, "cmd: %s\n", buf); + + fghn = popen(buf, "r"); + if (fghn == NULL) { + printerr(0, "failed to call %s\n", gethostname_ex); + return -1; + } + + head = fgets(buf, bufsize, fghn); + if (head == NULL) { + printerr(0, "can't read\n"); + return -1; + } + if (pclose(fghn) == -1) + printerr(1, "pclose failed, continue\n"); + + /* trim head/tail space */ + while (is_space(*head)) + head++; + + tail = head + strlen(head); + if (tail <= head) { + printerr(0, "no output\n"); + return -1; + } + while (is_space(*(tail - 1))) + tail--; + if (tail <= head) { + printerr(0, "output are all space\n"); + return -1; + } + *tail = '\0'; + + /* start with '@' means error msg */ + if (head[0] == '@') { + printerr(0, "%s\n", &head[1]); + return -1; + } + + if (tail - head > namebuflen) { + printerr(0, "hostname too long: %s\n", head); + return -1; + } + + printerr(2, "%s: net 0x%x, addr 0x%x => %s\n", + nal, net, addr, head); + strcpy(namebuf, head); + return 0; +} + +enum { + QSWNAL = 1, + SOCKNAL = 2, + GMNAL = 3, + /* 4 unused */ + TCPNAL = 5, + ROUTER = 6, + OPENIBNAL = 7, + IIBNAL = 8, + LONAL = 9, + RANAL = 10, + VIBNAL = 11, + NAL_ENUM_END_MARKER +}; + +static struct { + char *name; + ptl_nid2hostname_t *nid2name; +} converter[NAL_ENUM_END_MARKER] = { + {"UNUSED0", NULL}, + {"QSWNAL", external_nid2hostname}, + {"SOCKNAL", socknal_nid2hostname}, + {"GMNAL", external_nid2hostname}, + {"UNUSED4", NULL}, + {"TCPNAL", NULL}, + {"ROUTER", NULL}, + {"OPENIBNAL", external_nid2hostname}, + {"IIBNAL", external_nid2hostname}, + {"LONAL", lonal_nid2hostname}, + {"RANAL", NULL}, + {"VIBNAL", external_nid2hostname}, +}; + +int ptl_nid2hostname(uint64_t nid, char *buf, int buflen) +{ + uint32_t nal, net, addr; + + addr = LNET_NIDADDR(nid); + net = LNET_NIDNET(nid); + nal = LNET_NETTYP(net); + + if (nal >= NAL_ENUM_END_MARKER) { + printerr(0, "ERROR: Unrecognized NAL %u\n", nal); + return -1; + } + + if (converter[nal].nid2name == NULL) { + printerr(0, "ERROR: NAL %s converter not ready\n", + converter[nal].name); + return -1; + } + + return converter[nal].nid2name(converter[nal].name, net, addr, + buf, buflen); +} + + +/**************************************** + * portals support routine * + ****************************************/ + +static struct hostent * +ptl_gethostbyname(char * hname) { + struct hostent *he; + + he = gethostbyname(hname); + if (!he) { + switch(h_errno) { + case HOST_NOT_FOUND: + case NO_ADDRESS: + printerr(0, "Unable to resolve hostname: %s\n", + hname); + break; + default: + printerr(0, "gethostbyname %s: %s\n", + hname, strerror(h_errno)); + break; + } + return NULL; + } + return he; +} + +int +ptl_parse_ipquad (uint32_t *ipaddrp, char *str) +{ + int a; + int b; + int c; + int d; + + if (sscanf(str, "%d.%d.%d.%d", &a, &b, &c, &d) == 4 && + (a & ~0xff) == 0 && (b & ~0xff) == 0 && + (c & ~0xff) == 0 && (d & ~0xff) == 0) + { + *ipaddrp = (a<<24)|(b<<16)|(c<<8)|d; + return (0); + } + + return (-1); +} + +int +ptl_parse_ipaddr (uint32_t *ipaddrp, char *str) +{ + struct hostent *he; + + if (!strcmp (str, "_all_")) { + *ipaddrp = 0; + return (0); + } + + if (ptl_parse_ipquad(ipaddrp, str) == 0) + return (0); + + if ((('a' <= str[0] && str[0] <= 'z') || + ('A' <= str[0] && str[0] <= 'Z')) && + (he = ptl_gethostbyname (str)) != NULL) { + uint32_t addr = *(uint32_t *)he->h_addr; + + *ipaddrp = ntohl(addr); /* HOST byte order */ + return (0); + } + + return (-1); +} + +int +ptl_parse_nid (ptl_nid_t *nidp, char *str) +{ + uint32_t ipaddr; + char *end; + unsigned long long ullval; + + if (ptl_parse_ipaddr (&ipaddr, str) == 0) { +#if !CRAY_PORTALS + *nidp = (ptl_nid_t)ipaddr; +#else + *nidp = (((ptl_nid_t)ipaddr & PNAL_HOSTID_MASK) << PNAL_VNODE_SHIFT); +#endif + return (0); + } + + ullval = strtoull(str, &end, 0); + if (end != str && *end == 0) { + /* parsed whole non-empty string */ + *nidp = (ptl_nid_t)ullval; + return (0); + } + + return (-1); +} + + +/**************************************** + * user mapping database handling * + * (very rudiment) * + ****************************************/ + +#define MAPPING_GROW_SIZE 512 +#define MAX_LINE_LEN 1024 + +struct user_map_item { + char *principal; /* NULL means match all */ + ptl_netid_t netid; + ptl_nid_t nid; + uid_t uid; +}; + +struct user_mapping { + int size; + int nitems; + struct user_map_item *items; +}; + +static struct user_mapping mapping = {0, 0, NULL}; +/* FIXME to be finished: monitor change of mapping database */ +static int mapping_changed = 1; + +static +void cleanup_mapping(void) +{ + int n; + + for (n = 0; n < mapping.nitems; n++) { + assert(mapping.items[n].principal); + free(mapping.items[n].principal); + } + mapping.nitems = 0; +} + +static +int grow_mapping(int size) +{ + struct user_map_item *new; + int newsize; + + if (size <= mapping.size) + return 0; + + newsize = mapping.size + MAPPING_GROW_SIZE; + while (newsize < size) + newsize += MAPPING_GROW_SIZE; + + new = malloc(newsize * sizeof(struct user_map_item)); + if (!new) { + printerr(0, "can't alloc mapping size %d\n", newsize); + return -1; + } + memcpy(new, mapping.items, mapping.nitems * sizeof(void*)); + free(mapping.items); + mapping.items = new; + mapping.size = newsize; + return 0; +} + +uid_t parse_uid(char *uidstr) +{ + struct passwd *pw; + char *p = NULL; + long uid; + + pw = getpwnam(uidstr); + if (pw) + return pw->pw_uid; + + uid = strtol(uidstr, &p, 0); + if (*p == '\0') + return (uid_t) uid; + + return -1; +} + +static +int read_mapping_db(void) +{ + char princ[MAX_LINE_LEN]; + char nid_str[MAX_LINE_LEN]; + char dest[MAX_LINE_LEN]; + ptl_nid_t ptl_nid; + uid_t dest_uid; + FILE *f; + char *line, linebuf[MAX_LINE_LEN]; + + /* cleanup old mappings */ + cleanup_mapping(); + + f = fopen(MAPPING_DATABASE_FILE, "r"); + if (!f) { + printerr(0, "can't open mapping database: %s\n", + MAPPING_DATABASE_FILE); + return -1; + } + + while ((line = fgets(linebuf, MAX_LINE_LEN, f))) { + char *name; + + if (strlen(line) >= MAX_LINE_LEN) { + printerr(0, "invalid mapping db: line too long (%d)\n", + strlen(line)); + cleanup_mapping(); + fclose(f); + return -1; + } + if (sscanf(line, "%s %s %s", princ, nid_str, dest) != 3) { + printerr(0, "mapping db: syntax error\n"); + cleanup_mapping(); + fclose(f); + return -1; + } + if (grow_mapping(mapping.nitems + 1)) { + printerr(0, "fail to grow mapping to %d\n", + mapping.nitems + 1); + fclose(f); + return -1; + } + if (!strcmp(princ, "*")) { + name = NULL; + } else { + name = strdup(princ); + if (!name) { + printerr(0, "fail to dup str %s\n", princ); + fclose(f); + return -1; + } + } + if (ptl_parse_nid(&ptl_nid, nid_str)) { + printerr(0, "fail to parse nid %s\n", nid_str); + fclose(f); + return -1; + } + dest_uid = parse_uid(dest); + if (dest_uid == -1) { + printerr(0, "no valid user: %s\n", dest); + free(name); + fclose(f); + return -1; + } + + mapping.items[mapping.nitems].principal = name; + mapping.items[mapping.nitems].netid = 0; + mapping.items[mapping.nitems].nid = ptl_nid; + mapping.items[mapping.nitems].uid = dest_uid; + mapping.nitems++; + printerr(1, "add mapping: %s(%s/0x%llx) ==> %d\n", + name ? name : "*", nid_str, ptl_nid, dest_uid); + } + + return 0; +} + +int lookup_mapping(char *princ, uint32_t nal, ptl_netid_t netid, + ptl_nid_t nid, uid_t *uid) +{ + int n; + + /* FIXME race condition here */ + if (mapping_changed) { + if (read_mapping_db()) + printerr(0, "all remote users will be denied\n"); + mapping_changed = 0; + } + + for (n = 0; n < mapping.nitems; n++) { + struct user_map_item *entry = &mapping.items[n]; + + if (entry->netid != netid || entry->nid != nid) + continue; + if (!entry->principal || + !strcasecmp(entry->principal, princ)) { + printerr(1, "found mapping: %s ==> %d\n", + princ, entry->uid); + *uid = entry->uid; + return 0; + } + } + printerr(1, "no mapping for %s\n", princ); + *uid = -1; + return -1; +} + diff --git a/lustre/utils/gss/lsupport.h b/lustre/utils/gss/lsupport.h new file mode 100644 index 0000000..4945da8 --- /dev/null +++ b/lustre/utils/gss/lsupport.h @@ -0,0 +1,68 @@ +/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*- + * vim:expandtab:shiftwidth=8:tabstop=8: + */ + +#ifndef __LIBCFS_H__ +#define __LIBCFS_H__ + +#include +#include + +#define GSSD_CLI (0) +#define GSSD_SVC (1) + +void gssd_init_unique(int type); +void gssd_exit_unique(int type); + +/* + * copied from lustre source + */ + +typedef uint64_t ptl_nid_t; +typedef uint32_t ptl_netid_t; + +#define LUSTRE_GSS_SVC_MDS 0 +#define LUSTRE_GSS_SVC_OSS 1 + +struct lgssd_upcall_data { + uint32_t seq; + uint32_t uid; + uint32_t gid; + uint32_t svc; + uint64_t nid; + char obd[64]; +}; + +#define GSSD_INTERFACE_VERSION (1) + +struct lgssd_ioctl_param { + int version; /* in */ + char *uuid; /* in */ + int lustre_svc; /* in */ + uid_t uid; /* in */ + gid_t gid; /* in */ + long send_token_size;/* in */ + char *send_token; /* in */ + long reply_buf_size; /* in */ + char *reply_buf; /* in */ + long status; /* out */ + long reply_length; /* out */ +}; + +#define GSSD_DEFAULT_GETHOSTNAME_EX "/etc/lustre/nid2hostname" +#define MAPPING_DATABASE_FILE "/etc/lustre/idmap.conf" + +int ptl_nid2hostname(uint64_t nid, char *buf, int buflen); +int lookup_mapping(char *princ, uint32_t nal, ptl_netid_t netid, + ptl_nid_t nid, uid_t *uid); + +/* how an LNET NID encodes net:address */ +#define LNET_NIDADDR(nid) ((uint32_t)((nid) & 0xffffffff)) +#define LNET_NIDNET(nid) ((uint32_t)(((nid) >> 32)) & 0xffffffff) +#define LNET_MKNID(net,addr) ((((uint64_t)(net))<<32)|((uint64_t)(addr))) +/* how net encodes type:number */ +#define LNET_NETNUM(net) ((net) & 0xffff) +#define LNET_NETTYP(net) (((net) >> 16) & 0xffff) +#define LNET_MKNET(typ,num) ((((uint32_t)(typ))<<16)|((uint32_t)(num))) + +#endif /* __LIBCFS_H__ */ diff --git a/lustre/utils/gss/nfs-utils-1.0.10-lustre.diff b/lustre/utils/gss/nfs-utils-1.0.10-lustre.diff new file mode 100644 index 0000000..1055fc5 --- /dev/null +++ b/lustre/utils/gss/nfs-utils-1.0.10-lustre.diff @@ -0,0 +1,3309 @@ +--- nfs-utils-1.0.10/utils/gssd/context.c.lustre 2006-08-07 00:40:50.000000000 -0600 ++++ nfs-utils-1.0.10/utils/gssd/context.c 2006-08-14 10:32:30.000000000 -0600 +@@ -33,8 +33,6 @@ + #include + #include + #include +-#include +-#include + #include "gss_util.h" + #include "gss_oids.h" + #include "err_util.h" +--- nfs-utils-1.0.10/utils/gssd/context.h.lustre 2006-08-07 00:40:50.000000000 -0600 ++++ nfs-utils-1.0.10/utils/gssd/context.h 2006-08-14 10:32:30.000000000 -0600 +@@ -31,8 +31,6 @@ + #ifndef _CONTEXT_H_ + #define _CONTEXT_H_ + +-#include +- + int serialize_context_for_kernel(gss_ctx_id_t ctx, gss_buffer_desc *buf, + gss_OID mech); + int serialize_spkm3_ctx(gss_ctx_id_t ctx, gss_buffer_desc *buf); +--- nfs-utils-1.0.10/utils/gssd/context_mit.c.lustre 2006-08-14 10:32:04.000000000 -0600 ++++ nfs-utils-1.0.10/utils/gssd/context_mit.c 2006-08-14 10:32:30.000000000 -0600 +@@ -34,8 +34,6 @@ + #include + #include + #include +-#include +-#include + #include "gss_util.h" + #include "gss_oids.h" + #include "err_util.h" +@@ -468,6 +466,7 @@ + else + keyptr = &lctx->cfx_kd.ctx_key; + ++#if 0 + if (lctx->initiate == 1) { + sign_usage = KG_USAGE_INITIATOR_SIGN; + seal_usage = KG_USAGE_INITIATOR_SEAL; +@@ -475,6 +474,19 @@ + sign_usage = KG_USAGE_ACCEPTOR_SIGN; + seal_usage = KG_USAGE_ACCEPTOR_SEAL; + } ++#else ++ /* FIXME ++ * These are from rfc4142, but I don't understand: if we supply ++ * different 'usage' value for client & server, then the peers ++ * will have different derived keys. How could this work? ++ * ++ * Here we simply use old SIGN/SEAL values until we find the ++ * answer. --ericm ++ * FIXME ++ */ ++ sign_usage = KG_USAGE_SIGN; ++ seal_usage = KG_USAGE_SEAL; ++#endif + + /* derive and send down: Ke, Ki, and Kc */ + +@@ -793,7 +805,10 @@ + + out_err: + printerr(0, "ERROR: failed serializing krb5 context for kernel\n"); +- if (buf->value) free(buf->value); ++ if (buf->value) { ++ free(buf->value); ++ buf->value = NULL; ++ } + buf->length = 0; + return -1; + } +--- nfs-utils-1.0.10/utils/gssd/context_spkm3.c.lustre 2006-08-14 10:32:04.000000000 -0600 ++++ nfs-utils-1.0.10/utils/gssd/context_spkm3.c 2006-08-14 10:32:30.000000000 -0600 +@@ -33,8 +33,6 @@ + #include + #include + #include +-#include +-#include + #include "gss_util.h" + #include "gss_oids.h" + #include "err_util.h" +--- nfs-utils-1.0.10/utils/gssd/err_util.c.lustre 2006-08-07 00:40:50.000000000 -0600 ++++ nfs-utils-1.0.10/utils/gssd/err_util.c 2006-08-14 10:32:30.000000000 -0600 +@@ -32,6 +32,8 @@ + #include + #include + #include ++#include ++#include + #include "err_util.h" + + static int verbosity = 0; +@@ -91,3 +93,40 @@ + /* reset the buffer */ + memset(message_buf, 0, sizeof(message_buf)); + } ++ ++void print_hexl(int pri, unsigned char *cp, int length) ++{ ++ int i, j, jm; ++ unsigned char c; ++ ++ printerr(pri, "length %d\n",length); ++ printerr(pri, "\n"); ++ ++ for (i = 0; i < length; i += 0x10) { ++ printerr(pri, " %04x: ", (u_int)i); ++ jm = length - i; ++ jm = jm > 16 ? 16 : jm; ++ ++ for (j = 0; j < jm; j++) { ++ if ((j % 2) == 1) ++ printerr(pri,"%02x ", (u_int)cp[i+j]); ++ else ++ printerr(pri,"%02x", (u_int)cp[i+j]); ++ } ++ for (; j < 16; j++) { ++ if ((j % 2) == 1) ++ printerr(pri," "); ++ else ++ printerr(pri," "); ++ } ++ printerr(pri," "); ++ ++ for (j = 0; j < jm; j++) { ++ c = cp[i+j]; ++ c = isprint(c) ? c : '.'; ++ printerr(pri,"%c", c); ++ } ++ printerr(pri,"\n"); ++ } ++} ++ +--- nfs-utils-1.0.10/utils/gssd/err_util.h.lustre 2006-08-07 00:40:50.000000000 -0600 ++++ nfs-utils-1.0.10/utils/gssd/err_util.h 2006-08-14 10:32:30.000000000 -0600 +@@ -33,5 +33,6 @@ + + void initerr(char *progname, int verbosity, int fg); + void printerr(int priority, char *format, ...); ++void print_hexl(int pri, unsigned char *cp, int length); + + #endif /* _ERR_UTIL_H_ */ +--- nfs-utils-1.0.10/utils/gssd/gss_clnt_send_err.c.lustre 2006-08-07 00:40:50.000000000 -0600 ++++ nfs-utils-1.0.10/utils/gssd/gss_clnt_send_err.c 2006-08-14 10:32:30.000000000 -0600 +@@ -47,6 +47,7 @@ + #include "gssd.h" + #include "write_bytes.h" + ++#if 0 + char pipefsdir[PATH_MAX] = GSSD_PIPEFS_DIR; + + static void +@@ -102,3 +103,4 @@ + } + exit(0); + } ++#endif +--- nfs-utils-1.0.10/utils/gssd/gssd.c.lustre 2006-08-14 10:32:04.000000000 -0600 ++++ nfs-utils-1.0.10/utils/gssd/gssd.c 2006-08-14 10:32:30.000000000 -0600 +@@ -40,7 +40,6 @@ + + #include + #include +-#include + + #include + #include +@@ -52,6 +51,7 @@ + #include "err_util.h" + #include "gss_util.h" + #include "krb5_util.h" ++#include "lsupport.h" + + char pipefsdir[PATH_MAX] = GSSD_PIPEFS_DIR; + char keytabfile[PATH_MAX] = GSSD_DEFAULT_KEYTAB_FILE; +@@ -77,7 +77,7 @@ + static void + usage(char *progname) + { +- fprintf(stderr, "usage: %s [-f] [-v] [-r] [-p pipefsdir] [-k keytab] [-d ccachedir]\n", ++ fprintf(stderr, "usage: %s [-f] [-v] [-p pipefsdir] [-k keytab] [-d ccachedir]\n", + progname); + exit(1); + } +@@ -87,7 +87,6 @@ + { + int fg = 0; + int verbosity = 0; +- int rpc_verbosity = 0; + int opt; + extern char *optarg; + char *progname; +@@ -97,15 +96,9 @@ + case 'f': + fg = 1; + break; +- case 'm': +- /* Accept but ignore this. Now the default. */ +- break; + case 'v': + verbosity++; + break; +- case 'r': +- rpc_verbosity++; +- break; + case 'p': + strncpy(pipefsdir, optarg, sizeof(pipefsdir)); + if (pipefsdir[sizeof(pipefsdir)-1] != '\0') +@@ -126,10 +119,6 @@ + break; + } + } +- strncat(pipefsdir + strlen(pipefsdir), "/" GSSD_SERVICE_NAME, +- sizeof(pipefsdir)-strlen(pipefsdir)); +- if (pipefsdir[sizeof(pipefsdir)-1] != '\0') +- errx(1, "pipefs path name too long"); + + if ((progname = strrchr(argv[0], '/'))) + progname++; +@@ -137,30 +126,34 @@ + progname = argv[0]; + + initerr(progname, verbosity, fg); +-#ifdef HAVE_AUTHGSS_SET_DEBUG_LEVEL +- authgss_set_debug_level(rpc_verbosity); +-#else +- if (rpc_verbosity > 0) +- printerr(0, "Warning: rpcsec_gss library does not " +- "support setting debug level\n"); +-#endif + + if (gssd_check_mechs() != 0) + errx(1, "Problem with gssapi library"); + ++#if 0 ++ /* Determine Kerberos information from the kernel */ ++ gssd_obtain_kernel_krb5_info(); ++#endif ++ + if (!fg && daemon(0, 0) < 0) + errx(1, "fork"); + ++ /* This should be checked _after_ daemon(), because we need to own ++ * the undo-able semaphore by this process ++ */ ++ gssd_init_unique(GSSD_CLI); ++ ++ /* Process keytab file and get machine credentials. This will modify ++ * disk status so do it after we are sure we are the only instance ++ */ ++ if (gssd_refresh_krb5_machine_creds()) ++ return -1; ++ + signal(SIGINT, sig_die); + signal(SIGTERM, sig_die); + signal(SIGHUP, sig_hup); + +- /* Process keytab file and get machine credentials */ +- gssd_refresh_krb5_machine_creds(); +- /* Determine Kerberos information from the kernel */ +- gssd_obtain_kernel_krb5_info(); +- +- gssd_run(); ++ lgssd_run(); + printerr(0, "gssd_run returned!\n"); + abort(); + } +--- nfs-utils-1.0.10/utils/gssd/gssd.h.lustre 2006-08-07 00:40:50.000000000 -0600 ++++ nfs-utils-1.0.10/utils/gssd/gssd.h 2006-08-14 10:32:30.000000000 -0600 +@@ -48,8 +48,13 @@ + #define GSSD_DEFAULT_CRED_PREFIX "krb5cc_" + #define GSSD_DEFAULT_MACHINE_CRED_SUFFIX "machine" + #define GSSD_DEFAULT_KEYTAB_FILE "/etc/krb5.keytab" +-#define GSSD_SERVICE_NAME "nfs" +-#define GSSD_SERVICE_NAME_LEN 3 ++#define GSSD_SERVICE_MDS "lustre_mds" ++#define GSSD_SERVICE_OSS "lustre_oss" ++#define GSSD_SERVICE_MDS_NAMELEN 10 ++#define GSSD_SERVICE_OSS_NAMELEN 10 ++ ++#define LUSTRE_ROOT_NAME "lustre_root" ++#define LUSTRE_ROOT_NAMELEN 11 + + /* + * The gss mechanisms that we can handle +@@ -61,6 +66,7 @@ + extern char pipefsdir[PATH_MAX]; + extern char keytabfile[PATH_MAX]; + extern char ccachedir[PATH_MAX]; ++extern char gethostname_ex[PATH_MAX]; + + TAILQ_HEAD(clnt_list_head, clnt_info) clnt_list; + +@@ -69,10 +75,6 @@ + char *dirname; + int dir_fd; + char *servicename; +- char *servername; +- int prog; +- int vers; +- char *protocol; + int krb5_fd; + int krb5_poll_index; + int spkm3_fd; +@@ -83,8 +85,7 @@ + int update_client_list(void); + void handle_krb5_upcall(struct clnt_info *clp); + void handle_spkm3_upcall(struct clnt_info *clp); +-int gssd_acquire_cred(char *server_name); +-void gssd_run(void); ++void lgssd_run(void); + + + #endif /* _RPC_GSSD_H_ */ +--- nfs-utils-1.0.10/utils/gssd/gssd_main_loop.c.lustre 2006-08-07 00:40:50.000000000 -0600 ++++ nfs-utils-1.0.10/utils/gssd/gssd_main_loop.c 2006-08-14 10:32:30.000000000 -0600 +@@ -94,7 +94,7 @@ + }; + + void +-gssd_run() ++lgssd_run() + { + int ret; + struct sigaction dn_act; +@@ -118,6 +118,7 @@ + + while (1) { + while (dir_changed) { ++ printerr(2, "pipefs root dir changed\n"); + dir_changed = 0; + if (update_client_list()) { + printerr(0, "ERROR: couldn't update " +--- nfs-utils-1.0.10/utils/gssd/gssd_proc.c.lustre 2006-08-07 00:40:50.000000000 -0600 ++++ nfs-utils-1.0.10/utils/gssd/gssd_proc.c 2006-08-14 10:32:30.000000000 -0600 +@@ -43,7 +43,6 @@ + #endif + #include "config.h" + #include +-#include + #include + #include + #include +@@ -68,6 +67,7 @@ + #include "gss_oids.h" + #include "krb5_util.h" + #include "context.h" ++#include "lsupport.h" + + /* + * pollarray: +@@ -98,83 +98,6 @@ + + int pollsize; /* the size of pollaray (in pollfd's) */ + +-/* XXX buffer problems: */ +-static int +-read_service_info(char *info_file_name, char **servicename, char **servername, +- int *prog, int *vers, char **protocol) { +-#define INFOBUFLEN 256 +- char buf[INFOBUFLEN]; +- static char dummy[128]; +- int nbytes; +- static char service[128]; +- static char address[128]; +- char program[16]; +- char version[16]; +- char protoname[16]; +- in_addr_t inaddr; +- int fd = -1; +- struct hostent *ent = NULL; +- int numfields; +- +- *servicename = *servername = *protocol = NULL; +- +- if ((fd = open(info_file_name, O_RDONLY)) == -1) { +- printerr(0, "ERROR: can't open %s: %s\n", info_file_name, +- strerror(errno)); +- goto fail; +- } +- if ((nbytes = read(fd, buf, INFOBUFLEN)) == -1) +- goto fail; +- close(fd); +- +- numfields = sscanf(buf,"RPC server: %127s\n" +- "service: %127s %15s version %15s\n" +- "address: %127s\n" +- "protocol: %15s\n", +- dummy, +- service, program, version, +- address, +- protoname); +- +- if (numfields == 5) { +- strcpy(protoname, "tcp"); +- } else if (numfields != 6) { +- goto fail; +- } +- +- /* check service, program, and version */ +- if(memcmp(service, "nfs", 3)) return -1; +- *prog = atoi(program + 1); /* skip open paren */ +- *vers = atoi(version); +- if((*prog != 100003) || ((*vers != 2) && (*vers != 3) && (*vers != 4))) +- goto fail; +- +- /* create service name */ +- inaddr = inet_addr(address); +- if (!(ent = gethostbyaddr(&inaddr, sizeof(inaddr), AF_INET))) { +- printerr(0, "ERROR: can't resolve server %s name\n", address); +- goto fail; +- } +- if (!(*servername = calloc(strlen(ent->h_name) + 1, 1))) +- goto fail; +- memcpy(*servername, ent->h_name, strlen(ent->h_name)); +- snprintf(buf, INFOBUFLEN, "%s@%s", service, ent->h_name); +- if (!(*servicename = calloc(strlen(buf) + 1, 1))) +- goto fail; +- memcpy(*servicename, buf, strlen(buf)); +- +- if (!(*protocol = strdup(protoname))) +- goto fail; +- return 0; +-fail: +- printerr(0, "ERROR: failed to read service info\n"); +- if (fd != -1) close(fd); +- if (*servername) free(*servername); +- if (*servicename) free(*servicename); +- if (*protocol) free(*protocol); +- return -1; +-} +- + static void + destroy_client(struct clnt_info *clp) + { +@@ -189,8 +112,6 @@ + if (clp->spkm3_fd != -1) close(clp->spkm3_fd); + if (clp->dirname) free(clp->dirname); + if (clp->servicename) free(clp->servicename); +- if (clp->servername) free(clp->servername); +- if (clp->protocol) free(clp->protocol); + free(clp); + } + +@@ -220,7 +141,6 @@ + { + char kname[32]; + char sname[32]; +- char info_file_name[32]; + + if (clp->krb5_fd == -1) { + snprintf(kname, sizeof(kname), "%s/krb5", clp->dirname); +@@ -232,13 +152,6 @@ + } + if((clp->krb5_fd == -1) && (clp->spkm3_fd == -1)) + return -1; +- snprintf(info_file_name, sizeof(info_file_name), "%s/info", +- clp->dirname); +- if ((clp->servicename == NULL) && +- read_service_info(info_file_name, &clp->servicename, +- &clp->servername, &clp->prog, &clp->vers, +- &clp->protocol)) +- return -1; + return 0; + } + +@@ -272,6 +185,8 @@ + } + pollarray[clp->krb5_poll_index].fd = clp->krb5_fd; + pollarray[clp->krb5_poll_index].events |= POLLIN; ++ printerr(2, "monitoring krb5 channel under %s\n", ++ clp->dirname); + } + + if ((clp->spkm3_fd != -1) && (clp->spkm3_poll_index == -1)) { +@@ -385,7 +300,10 @@ + int + update_client_list(void) + { +- struct dirent **namelist; ++ char lustre_dir[PATH_MAX]; ++ struct dirent lustre_dirent = { .d_name = "lustre" }; ++ struct dirent *namelist[1]; ++ struct stat statbuf; + int i, j; + + if (chdir(pipefsdir) < 0) { +@@ -394,45 +312,76 @@ + return -1; + } + +- j = scandir(pipefsdir, &namelist, NULL, alphasort); +- if (j < 0) { +- printerr(0, "ERROR: can't scandir %s: %s\n", +- pipefsdir, strerror(errno)); +- return -1; ++ snprintf(lustre_dir, sizeof(lustre_dir), "%s/%s", pipefsdir, "lustre"); ++ if (stat(lustre_dir, &statbuf) == 0) { ++ namelist[0] = &lustre_dirent; ++ j = 1; ++ printerr(2, "re-processing lustre directory\n"); ++ } else { ++ namelist[0] = NULL; ++ j = 0; ++ printerr(2, "lustre directory not exist\n"); + } ++ + update_old_clients(namelist, j); + for (i=0; i < j; i++) { +- if (i < FD_ALLOC_BLOCK +- && !strncmp(namelist[i]->d_name, "clnt", 4) +- && !find_client(namelist[i]->d_name)) ++ if (i < FD_ALLOC_BLOCK && ++ !find_client(namelist[i]->d_name)) + process_clnt_dir(namelist[i]->d_name); +- free(namelist[i]); + } + +- free(namelist); ++ chdir("/"); + return 0; + } + ++/* Context creation response. */ ++struct lustre_gss_init_res { ++ gss_buffer_desc gr_ctx; /* context handle */ ++ u_int gr_major; /* major status */ ++ u_int gr_minor; /* minor status */ ++ u_int gr_win; /* sequence window */ ++ gss_buffer_desc gr_token; /* token */ ++}; ++ ++struct lustre_gss_data { ++ int lgd_established; ++ int lgd_lustre_svc; /* mds/oss */ ++ int lgd_uid; /* uid */ ++ char *lgd_uuid; /* client device uuid */ ++ gss_name_t lgd_name; /* service name */ ++ ++ gss_OID lgd_mech; /* mech OID */ ++ u_int lgd_req_flags; /* request flags */ ++ gss_cred_id_t lgd_cred; /* credential */ ++ gss_ctx_id_t lgd_ctx; /* session context */ ++ gss_buffer_desc lgd_rmt_ctx; /* remote handle of context */ ++ uint32_t lgd_seq_win; /* sequence window */ ++ ++ int lgd_rpc_err; ++ int lgd_gss_err; ++}; ++ + static int +-do_downcall(int k5_fd, uid_t uid, struct authgss_private_data *pd, +- gss_buffer_desc *context_token) ++do_downcall(int k5_fd, struct lgssd_upcall_data *updata, ++ struct lustre_gss_data *lgd, gss_buffer_desc *context_token) + { + char *buf = NULL, *p = NULL, *end = NULL; + unsigned int timeout = 0; /* XXX decide on a reasonable value */ + unsigned int buf_size = 0; + +- printerr(1, "doing downcall\n"); +- buf_size = sizeof(uid) + sizeof(timeout) + sizeof(pd->pd_seq_win) + +- sizeof(pd->pd_ctx_hndl.length) + pd->pd_ctx_hndl.length + ++ printerr(2, "doing downcall\n"); ++ buf_size = sizeof(updata->seq) + sizeof(timeout) + ++ sizeof(lgd->lgd_seq_win) + ++ sizeof(lgd->lgd_rmt_ctx.length) + lgd->lgd_rmt_ctx.length + + sizeof(context_token->length) + context_token->length; + p = buf = malloc(buf_size); + end = buf + buf_size; + +- if (WRITE_BYTES(&p, end, uid)) goto out_err; ++ if (WRITE_BYTES(&p, end, updata->seq)) goto out_err; + /* Not setting any timeout for now: */ + if (WRITE_BYTES(&p, end, timeout)) goto out_err; +- if (WRITE_BYTES(&p, end, pd->pd_seq_win)) goto out_err; +- if (write_buffer(&p, end, &pd->pd_ctx_hndl)) goto out_err; ++ if (WRITE_BYTES(&p, end, lgd->lgd_seq_win)) goto out_err; ++ if (write_buffer(&p, end, &lgd->lgd_rmt_ctx)) goto out_err; + if (write_buffer(&p, end, context_token)) goto out_err; + + if (write(k5_fd, buf, p - buf) < p - buf) goto out_err; +@@ -440,12 +389,13 @@ + return 0; + out_err: + if (buf) free(buf); +- printerr(0, "Failed to write downcall!\n"); ++ printerr(0, "ERROR: Failed to write downcall!\n"); + return -1; + } + + static int +-do_error_downcall(int k5_fd, uid_t uid, int err) ++do_error_downcall(int k5_fd, struct lgssd_upcall_data *updata, ++ int rpc_err, int gss_err) + { + char buf[1024]; + char *p = buf, *end = buf + 1024; +@@ -454,11 +404,12 @@ + + printerr(1, "doing error downcall\n"); + +- if (WRITE_BYTES(&p, end, uid)) goto out_err; ++ if (WRITE_BYTES(&p, end, updata->seq)) goto out_err; + if (WRITE_BYTES(&p, end, timeout)) goto out_err; + /* use seq_win = 0 to indicate an error: */ + if (WRITE_BYTES(&p, end, zero)) goto out_err; +- if (WRITE_BYTES(&p, end, err)) goto out_err; ++ if (WRITE_BYTES(&p, end, rpc_err)) goto out_err; ++ if (WRITE_BYTES(&p, end, gss_err)) goto out_err; + + if (write(k5_fd, buf, p - buf) < p - buf) goto out_err; + return 0; +@@ -467,6 +418,7 @@ + return -1; + } + ++#if 0 + /* + * Create an RPC connection and establish an authenticated + * gss context with a server. +@@ -658,7 +610,287 @@ + + goto out; + } ++#endif ++ ++static ++int do_negotiation(struct lustre_gss_data *lgd, ++ gss_buffer_desc *gss_token, ++ struct lustre_gss_init_res *gr, ++ int timeout) ++{ ++ char *file = "/proc/fs/lustre/gss/init_channel"; ++ struct lgssd_ioctl_param param; ++ struct passwd *pw; ++ int fd, ret; ++ char outbuf[8192]; ++ unsigned int *p; ++ int res; ++ ++ pw = getpwuid(lgd->lgd_uid); ++ if (!pw) { ++ printerr(0, "no uid %u in local user database\n", ++ lgd->lgd_uid); ++ return -1; ++ } ++ ++ param.version = GSSD_INTERFACE_VERSION; ++ param.uuid = lgd->lgd_uuid; ++ param.lustre_svc = lgd->lgd_lustre_svc; ++ param.uid = lgd->lgd_uid; ++ param.gid = pw->pw_gid; ++ param.send_token_size = gss_token->length; ++ param.send_token = (char *) gss_token->value; ++ param.reply_buf_size = sizeof(outbuf); ++ param.reply_buf = outbuf; ++ ++ fd = open(file, O_RDWR); ++ if (fd < 0) { ++ printerr(0, "can't open file %s\n", file); ++ return -1; ++ } ++ ++ ret = write(fd, ¶m, sizeof(param)); ++ ++ if (ret != sizeof(param)) { ++ printerr(0, "lustre ioctl err: %d\n", strerror(errno)); ++ close(fd); ++ return -1; ++ } ++ if (param.status) { ++ close(fd); ++ printerr(0, "status: %d (%s)\n", ++ param.status, strerror((int)param.status)); ++ if (param.status == -ETIMEDOUT) { ++ /* kernel return -ETIMEDOUT means the rpc timedout, ++ * we should notify the caller to reinitiate the ++ * gss negotiation, by return -ERESTART ++ */ ++ lgd->lgd_rpc_err = -ERESTART; ++ lgd->lgd_gss_err = 0; ++ } else { ++ lgd->lgd_rpc_err = param.status; ++ lgd->lgd_gss_err = 0; ++ } ++ return -1; ++ } ++ p = (unsigned int *)outbuf; ++ res = *p++; ++ gr->gr_major = *p++; ++ gr->gr_minor = *p++; ++ gr->gr_win = *p++; ++ ++ gr->gr_ctx.length = *p++; ++ gr->gr_ctx.value = malloc(gr->gr_ctx.length); ++ memcpy(gr->gr_ctx.value, p, gr->gr_ctx.length); ++ p += (((gr->gr_ctx.length + 3) & ~3) / 4); ++ ++ gr->gr_token.length = *p++; ++ gr->gr_token.value = malloc(gr->gr_token.length); ++ memcpy(gr->gr_token.value, p, gr->gr_token.length); ++ p += (((gr->gr_token.length + 3) & ~3) / 4); ++ ++ printerr(2, "do_negotiation: receive handle len %d, token len %d\n", ++ gr->gr_ctx.length, gr->gr_token.length); ++ close(fd); ++ return 0; ++} + ++static ++int gssd_refresh_lgd(struct lustre_gss_data *lgd) ++{ ++ struct lustre_gss_init_res gr; ++ gss_buffer_desc *recv_tokenp, send_token; ++ OM_uint32 maj_stat, min_stat, call_stat, ret_flags; ++ ++ /* GSS context establishment loop. */ ++ memset(&gr, 0, sizeof(gr)); ++ recv_tokenp = GSS_C_NO_BUFFER; ++ ++ for (;;) { ++ /* print the token we just received */ ++ if (recv_tokenp != GSS_C_NO_BUFFER) { ++ printerr(3, "The received token length %d\n", ++ recv_tokenp->length); ++ print_hexl(3, recv_tokenp->value, recv_tokenp->length); ++ } ++ ++ maj_stat = gss_init_sec_context(&min_stat, ++ lgd->lgd_cred, ++ &lgd->lgd_ctx, ++ lgd->lgd_name, ++ lgd->lgd_mech, ++ lgd->lgd_req_flags, ++ 0, /* time req */ ++ NULL, /* channel */ ++ recv_tokenp, ++ NULL, /* used mech */ ++ &send_token, ++ &ret_flags, ++ NULL); /* time rec */ ++ ++ if (recv_tokenp != GSS_C_NO_BUFFER) { ++ gss_release_buffer(&min_stat, &gr.gr_token); ++ recv_tokenp = GSS_C_NO_BUFFER; ++ } ++ if (maj_stat != GSS_S_COMPLETE && ++ maj_stat != GSS_S_CONTINUE_NEEDED) { ++ pgsserr("gss_init_sec_context", maj_stat, min_stat, ++ lgd->lgd_mech); ++ break; ++ } ++ if (send_token.length != 0) { ++ memset(&gr, 0, sizeof(gr)); ++ ++ /* print the token we are about to send */ ++ printerr(3, "token being sent length %d\n", ++ send_token.length); ++ print_hexl(3, send_token.value, send_token.length); ++ ++ call_stat = do_negotiation(lgd, &send_token, &gr, 0); ++ gss_release_buffer(&min_stat, &send_token); ++ ++ if (call_stat != 0 || ++ (gr.gr_major != GSS_S_COMPLETE && ++ gr.gr_major != GSS_S_CONTINUE_NEEDED)) { ++ printerr(0, "call stat %d, major stat 0x%x\n", ++ (int)call_stat, gr.gr_major); ++ return -1; ++ } ++ ++ if (gr.gr_ctx.length != 0) { ++ if (lgd->lgd_rmt_ctx.value) ++ gss_release_buffer(&min_stat, ++ &lgd->lgd_rmt_ctx); ++ lgd->lgd_rmt_ctx = gr.gr_ctx; ++ } ++ if (gr.gr_token.length != 0) { ++ if (maj_stat != GSS_S_CONTINUE_NEEDED) ++ break; ++ recv_tokenp = &gr.gr_token; ++ } ++ } ++ ++ /* GSS_S_COMPLETE => check gss header verifier, ++ * usually checked in gss_validate ++ */ ++ if (maj_stat == GSS_S_COMPLETE) { ++ lgd->lgd_established = 1; ++ lgd->lgd_seq_win = gr.gr_win; ++ break; ++ } ++ } ++ /* End context negotiation loop. */ ++ if (!lgd->lgd_established) { ++ if (gr.gr_token.length != 0) ++ gss_release_buffer(&min_stat, &gr.gr_token); ++ ++ printerr(0, "context negotiation failed\n"); ++ return -1; ++ } ++ ++ printerr(2, "successfully refreshed lgd\n"); ++ return 0; ++} ++ ++static ++int gssd_create_lgd(struct clnt_info *clp, ++ struct lustre_gss_data *lgd, ++ struct lgssd_upcall_data *updata, ++ int authtype) ++{ ++ gss_buffer_desc sname; ++ OM_uint32 maj_stat, min_stat; ++ int retval = -1; ++ ++ lgd->lgd_established = 0; ++ lgd->lgd_lustre_svc = updata->svc; ++ lgd->lgd_uid = updata->uid; ++ lgd->lgd_uuid = updata->obd; ++ ++ switch (authtype) { ++ case AUTHTYPE_KRB5: ++ lgd->lgd_mech = (gss_OID) &krb5oid; ++ lgd->lgd_req_flags = GSS_C_MUTUAL_FLAG; ++ break; ++ case AUTHTYPE_SPKM3: ++ lgd->lgd_mech = (gss_OID) &spkm3oid; ++ /* XXX sec.req_flags = GSS_C_ANON_FLAG; ++ * Need a way to switch.... ++ */ ++ lgd->lgd_req_flags = GSS_C_MUTUAL_FLAG; ++ break; ++ default: ++ printerr(0, "Invalid authentication type (%d)\n", authtype); ++ return -1; ++ } ++ ++ lgd->lgd_cred = GSS_C_NO_CREDENTIAL; ++ lgd->lgd_ctx = GSS_C_NO_CONTEXT; ++ lgd->lgd_rmt_ctx = (gss_buffer_desc) GSS_C_EMPTY_BUFFER; ++ lgd->lgd_seq_win = 0; ++ ++ sname.value = clp->servicename; ++ sname.length = strlen(clp->servicename); ++ ++ maj_stat = gss_import_name(&min_stat, &sname, ++ (gss_OID) GSS_C_NT_HOSTBASED_SERVICE, ++ &lgd->lgd_name); ++ if (maj_stat != GSS_S_COMPLETE) { ++ pgsserr(0, maj_stat, min_stat, lgd->lgd_mech); ++ goto out_fail; ++ } ++ ++ retval = gssd_refresh_lgd(lgd); ++ ++ if (lgd->lgd_name != GSS_C_NO_NAME) ++ gss_release_name(&min_stat, &lgd->lgd_name); ++ ++ if (lgd->lgd_cred != GSS_C_NO_CREDENTIAL) ++ gss_release_cred(&min_stat, &lgd->lgd_cred); ++ ++ out_fail: ++ return retval; ++} ++ ++static ++void gssd_free_lgd(struct lustre_gss_data *lgd) ++{ ++ gss_buffer_t token = GSS_C_NO_BUFFER; ++ OM_uint32 maj_stat, min_stat; ++ ++ if (lgd->lgd_ctx == GSS_C_NO_CONTEXT) ++ return; ++ ++ maj_stat = gss_delete_sec_context(&min_stat, &lgd->lgd_ctx, token); ++} ++ ++static ++int construct_service_name(struct clnt_info *clp, ++ struct lgssd_upcall_data *ud) ++{ ++ const int buflen = 256; ++ char name[buflen]; ++ ++ if (clp->servicename) { ++ free(clp->servicename); ++ clp->servicename = NULL; ++ } ++ ++ if (ptl_nid2hostname(ud->nid, name, buflen)) ++ return -1; ++ ++ clp->servicename = malloc(32 + strlen(name)); ++ if (!clp->servicename) { ++ printerr(0, "can't alloc memory\n"); ++ return -1; ++ } ++ sprintf(clp->servicename, "%s@%s", ++ ud->svc == LUSTRE_GSS_SVC_MDS ? ++ GSSD_SERVICE_MDS : GSSD_SERVICE_OSS, ++ name); ++ printerr(2, "constructed servicename: %s\n", clp->servicename); ++ return 0; ++} + + /* + * this code uses the userland rpcsec gss library to create a krb5 +@@ -667,27 +899,42 @@ + void + handle_krb5_upcall(struct clnt_info *clp) + { +- uid_t uid; +- CLIENT *rpc_clnt = NULL; +- AUTH *auth = NULL; +- struct authgss_private_data pd; + gss_buffer_desc token; ++ struct lgssd_upcall_data updata; ++ struct lustre_gss_data lgd; + char **credlist = NULL; + char **ccname; + +- printerr(1, "handling krb5 upcall\n"); ++ printerr(2, "handling krb5 upcall\n"); ++ ++ lgd.lgd_rpc_err = -EPERM; /* default error code */ + + token.length = 0; + token.value = NULL; +- memset(&pd, 0, sizeof(struct authgss_private_data)); + +- if (read(clp->krb5_fd, &uid, sizeof(uid)) < sizeof(uid)) { +- printerr(0, "WARNING: failed reading uid from krb5 " ++ if (read(clp->krb5_fd, &updata, sizeof(updata)) != sizeof(updata)) { ++ printerr(0, "WARNING: failed reading from krb5 " + "upcall pipe: %s\n", strerror(errno)); + goto out; + } + +- if (uid == 0) { ++ printerr(1, "krb5 upcall: seq %u, uid %u, svc %u, nid 0x%llx, obd %s\n", ++ updata.seq, updata.uid, updata.svc, updata.nid, updata.obd); ++ ++ if (updata.svc != LUSTRE_GSS_SVC_MDS && ++ updata.svc != LUSTRE_GSS_SVC_OSS) { ++ printerr(0, "invalid svc %d\n", updata.svc); ++ lgd.lgd_rpc_err = -EPROTO; ++ goto out_return_error; ++ } ++ updata.obd[sizeof(updata.obd)-1] = '\0'; ++ ++ if (construct_service_name(clp, &updata)) { ++ printerr(0, "failed to construct service name\n"); ++ goto out_return_error; ++ } ++ ++ if (updata.uid == 0) { + int success = 0; + + /* +@@ -695,75 +942,65 @@ + * of them until one works or we've tried them all + */ + if (gssd_get_krb5_machine_cred_list(&credlist)) { +- printerr(0, "WARNING: Failed to obtain machine " +- "credentials for connection to " +- "server %s\n", clp->servername); +- goto out_return_error; ++ printerr(0, "ERROR: Failed to obtain machine " ++ "credentials for %s\n", clp->servicename); ++ goto out_return_error; + } + for (ccname = credlist; ccname && *ccname; ccname++) { + gssd_setup_krb5_machine_gss_ccache(*ccname); +- if ((create_auth_rpc_client(clp, &rpc_clnt, &auth, uid, +- AUTHTYPE_KRB5)) == 0) { ++ if ((gssd_create_lgd(clp, &lgd, &updata, ++ AUTHTYPE_KRB5)) == 0) { + /* Success! */ + success++; + break; + } + printerr(2, "WARNING: Failed to create krb5 context " + "for user with uid %d with credentials " +- "cache %s for server %s\n", +- uid, *ccname, clp->servername); ++ "cache %s for service %s\n", ++ updata.uid, *ccname, clp->servicename); + } + gssd_free_krb5_machine_cred_list(credlist); + if (!success) { +- printerr(0, "WARNING: Failed to create krb5 context " ++ printerr(0, "ERROR: Failed to create krb5 context " + "for user with uid %d with any " +- "credentials cache for server %s\n", +- uid, clp->servername); ++ "credentials cache for service %s\n", ++ updata.uid, clp->servicename); + goto out_return_error; + } + } + else { + /* Tell krb5 gss which credentials cache to use */ +- gssd_setup_krb5_user_gss_ccache(uid, clp->servername); ++ gssd_setup_krb5_user_gss_ccache(updata.uid, clp->servicename); + +- if ((create_auth_rpc_client(clp, &rpc_clnt, &auth, uid, +- AUTHTYPE_KRB5)) != 0) { ++ if ((gssd_create_lgd(clp, &lgd, &updata, AUTHTYPE_KRB5)) != 0) { + printerr(0, "WARNING: Failed to create krb5 context " +- "for user with uid %d for server %s\n", +- uid, clp->servername); ++ "for user with uid %d for service %s\n", ++ updata.uid, clp->servicename); + goto out_return_error; + } + } + +- if (!authgss_get_private_data(auth, &pd)) { +- printerr(0, "WARNING: Failed to obtain authentication " +- "data for user with uid %d for server %s\n", +- uid, clp->servername); +- goto out_return_error; +- } +- +- if (serialize_context_for_kernel(pd.pd_ctx, &token, &krb5oid)) { ++ if (serialize_context_for_kernel(lgd.lgd_ctx, &token, &krb5oid)) { + printerr(0, "WARNING: Failed to serialize krb5 context for " +- "user with uid %d for server %s\n", +- uid, clp->servername); ++ "user with uid %d for service %s\n", ++ updata.uid, clp->servicename); + goto out_return_error; + } + +- do_downcall(clp->krb5_fd, uid, &pd, &token); ++ printerr(1, "refreshed: %u@%s for %s\n", ++ updata.uid, updata.obd, clp->servicename); ++ do_downcall(clp->krb5_fd, &updata, &lgd, &token); + + out: + if (token.value) + free(token.value); +- if (pd.pd_ctx_hndl.length != 0) +- authgss_free_private_data(&pd); +- if (auth) +- AUTH_DESTROY(auth); +- if (rpc_clnt) +- clnt_destroy(rpc_clnt); ++ ++ gssd_free_lgd(&lgd); + return; + + out_return_error: +- do_error_downcall(clp->krb5_fd, uid, -1); ++ do_error_downcall(clp->krb5_fd, &updata, ++ lgd.lgd_rpc_err, lgd.lgd_gss_err); + goto out; + } + +@@ -774,6 +1011,7 @@ + void + handle_spkm3_upcall(struct clnt_info *clp) + { ++#if 0 + uid_t uid; + CLIENT *rpc_clnt = NULL; + AUTH *auth = NULL; +@@ -825,4 +1063,5 @@ + out_return_error: + do_error_downcall(clp->spkm3_fd, uid, -1); + goto out; ++#endif + } +--- nfs-utils-1.0.10/utils/gssd/gss_util.c.lustre 2006-08-07 00:40:50.000000000 -0600 ++++ nfs-utils-1.0.10/utils/gssd/gss_util.c 2006-08-14 10:32:30.000000000 -0600 +@@ -87,9 +87,16 @@ + #ifdef HAVE_COM_ERR_H + #include + #endif ++#include "lsupport.h" + + /* Global gssd_credentials handle */ +-gss_cred_id_t gssd_creds; ++gss_cred_id_t gssd_cred_mds; ++gss_cred_id_t gssd_cred_oss; ++int gssd_cred_mds_valid = 0; ++int gssd_cred_oss_valid = 0; ++ ++char *mds_local_realm = NULL; ++char *oss_local_realm = NULL; + + gss_OID g_mechOid = GSS_C_NULL_OID;; + +@@ -183,15 +190,51 @@ + display_status_2(msg, maj_stat, min_stat, mech); + } + +-int +-gssd_acquire_cred(char *server_name) ++static ++int extract_realm_name(gss_buffer_desc *name, char **realm) ++{ ++ char *sname, *c; ++ ++ sname = malloc(name->length + 1); ++ if (!sname) { ++ printerr(0, "out of memory\n"); ++ return -ENOMEM; ++ } ++ ++ memcpy(sname, name->value, name->length); ++ sname[name->length] = '\0'; ++ printerr(1, "service principal: %s\n", sname); ++ ++ c = strchr(sname, '@'); ++ if (!realm) ++ *realm = NULL; ++ else { ++ c++; ++ *realm = malloc(strlen(c) + 1); ++ if (!*realm) { ++ printerr(0, "out of memory\n"); ++ return -ENOMEM; ++ } ++ strcpy(*realm, c); ++ } ++ free(sname); ++ ++ return 0; ++} ++ ++static ++int gssd_acquire_cred(char *server_name, gss_cred_id_t *cred, ++ char **local_realm, int *valid) + { + gss_buffer_desc name; + gss_name_t target_name; + u_int32_t maj_stat, min_stat; + u_int32_t ignore_maj_stat, ignore_min_stat; ++ gss_OID name_type; + gss_buffer_desc pbuf; + ++ *valid = 0; ++ + name.value = (void *)server_name; + name.length = strlen(server_name); + +@@ -201,12 +244,20 @@ + + if (maj_stat != GSS_S_COMPLETE) { + pgsserr("gss_import_name", maj_stat, min_stat, g_mechOid); +- return (FALSE); ++ return -1; ++ } ++ ++ maj_stat = gss_display_name(&min_stat, target_name, &name, &name_type); ++ if (maj_stat != GSS_S_COMPLETE) { ++ pgsserr(0, maj_stat, min_stat, g_mechOid); ++ return -1; + } ++ if (extract_realm_name(&name, local_realm)) ++ return -1; + + maj_stat = gss_acquire_cred(&min_stat, target_name, 0, + GSS_C_NULL_OID_SET, GSS_C_ACCEPT, +- &gssd_creds, NULL, NULL); ++ cred, NULL, NULL); + + if (maj_stat != GSS_S_COMPLETE) { + pgsserr("gss_acquire_cred", maj_stat, min_stat, g_mechOid); +@@ -218,11 +269,65 @@ + ignore_maj_stat = gss_release_buffer(&ignore_min_stat, + &pbuf); + } +- } ++ } else ++ *valid = 1; + + ignore_maj_stat = gss_release_name(&ignore_min_stat, &target_name); + +- return (maj_stat == GSS_S_COMPLETE); ++ if (maj_stat != GSS_S_COMPLETE) ++ return -1; ++ return 0; ++} ++ ++int gssd_prepare_creds(int must_srv_mds, int must_srv_oss) ++{ ++ if (gssd_acquire_cred(GSSD_SERVICE_MDS, &gssd_cred_mds, ++ &mds_local_realm, &gssd_cred_mds_valid)) { ++ if (must_srv_mds) ++ return -1; ++ } ++ ++ if (gssd_acquire_cred(GSSD_SERVICE_OSS, &gssd_cred_oss, ++ &oss_local_realm, &gssd_cred_oss_valid)) { ++ if (must_srv_oss) ++ return -1; ++ } ++ ++ if (!gssd_cred_mds_valid && !gssd_cred_oss_valid) { ++ printerr(0, "can't obtain both mds & oss creds, exit\n"); ++ return -1; ++ } ++ ++ printerr(0, "Ready to serve %s\n", ++ gssd_cred_mds_valid && !gssd_cred_oss_valid ? "Lustre MDS" : ++ (!gssd_cred_mds_valid && gssd_cred_oss_valid ? "Lustre OSS" : ++ "Lustre MDS and OSS")); ++ ++ return 0; ++} ++ ++gss_cred_id_t gssd_select_svc_cred(int lustre_svc) ++{ ++ switch (lustre_svc) { ++ case LUSTRE_GSS_SVC_MDS: ++ if (!gssd_cred_mds_valid) { ++ printerr(0, "ERROR: service cred for mds not ready\n"); ++ return NULL; ++ } ++ printerr(2, "select mds service cred\n"); ++ return gssd_cred_mds; ++ case LUSTRE_GSS_SVC_OSS: ++ if (!gssd_cred_oss_valid) { ++ printerr(0, "ERROR: service cred for oss not ready\n"); ++ return NULL; ++ } ++ printerr(2, "select oss service cred\n"); ++ return gssd_cred_oss; ++ default: ++ printerr(0, "ERROR: invalid lustre svc id %d\n", lustre_svc); ++ } ++ ++ return NULL; + } + + int gssd_check_mechs(void) +--- nfs-utils-1.0.10/utils/gssd/gss_util.h.lustre 2006-08-07 00:40:50.000000000 -0600 ++++ nfs-utils-1.0.10/utils/gssd/gss_util.h 2006-08-14 10:32:30.000000000 -0600 +@@ -32,12 +32,10 @@ + #define _GSS_UTIL_H_ + + #include +-#include + #include "write_bytes.h" + + extern gss_cred_id_t gssd_creds; + +-int gssd_acquire_cred(char *server_name); + void pgsserr(char *msg, u_int32_t maj_stat, u_int32_t min_stat, + const gss_OID mech); + int gssd_check_mechs(void); +--- nfs-utils-1.0.10/utils/gssd/krb5_util.c.lustre 2006-08-14 10:32:04.000000000 -0600 ++++ nfs-utils-1.0.10/utils/gssd/krb5_util.c 2006-08-14 10:32:30.000000000 -0600 +@@ -99,12 +99,14 @@ + #include + #include + #include ++#include + #include + #include + + #include + #include + #include ++#include + #include + #include + #include +@@ -114,7 +116,6 @@ + #include + #endif + #include +-#include + + #include "gssd.h" + #include "err_util.h" +@@ -129,6 +130,12 @@ + int num_krb5_enctypes = 0; + krb5_enctype *krb5_enctypes = NULL; + ++/* realm of this node */ ++char *this_realm = NULL; ++ ++/* credential expire time in advance */ ++unsigned long machine_cred_expire_advance = 300; /* 5 mins */ ++ + /*==========================*/ + /*=== Internal routines ===*/ + /*==========================*/ +@@ -137,11 +144,55 @@ + 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_have_realm_ple(void *realm); + 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 + * +@@ -292,7 +343,7 @@ + + memset(&my_creds, 0, sizeof(my_creds)); + +- if (ple->ccname && ple->endtime > now) { ++ 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; +@@ -323,11 +374,7 @@ + "principal '%s' from keytab '%s'\n", + error_message(code), + pname ? pname : "", kt_name); +-#ifdef HAVE_KRB5 +- if (pname) krb5_free_unparsed_name(context, pname); +-#else +- if (pname) free(pname); +-#endif ++ if (pname) KRB5_FREE_UNPARSED_NAME(context, pname); + goto out; + } + +@@ -371,15 +418,7 @@ + return (code); + } + +-/* +- * Determine if we already have a ple for the given realm +- * +- * Returns: +- * 0 => no ple found for given realm +- * 1 => found ple for given realm +- */ +-static int +-gssd_have_realm_ple(void *r) ++static struct gssd_k5_kt_princ * gssd_get_realm_ple(void *r) + { + struct gssd_k5_kt_princ *ple; + #ifdef HAVE_KRB5 +@@ -389,18 +428,76 @@ + #endif + + for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) { +-#ifdef HAVE_KRB5 +- if ((realm->length == strlen(ple->realm)) && +- (strncmp(realm->data, ple->realm, realm->length) == 0)) { +-#else +- if (strcmp(realm, ple->realm) == 0) { +-#endif +- return 1; +- } ++ 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. +@@ -444,82 +541,106 @@ + } + printerr(2, "Processing keytab entry for principal '%s'\n", + pname); +-#ifdef HAVE_KRB5 +- if ( (kte.principal->data[0].length == GSSD_SERVICE_NAME_LEN) && +- (strncmp(kte.principal->data[0].data, GSSD_SERVICE_NAME, +- GSSD_SERVICE_NAME_LEN) == 0) && +-#else +- if ( (strlen(kte.principal->name.name_string.val[0]) == GSSD_SERVICE_NAME_LEN) && +- (strncmp(kte.principal->name.name_string.val[0], GSSD_SERVICE_NAME, +- GSSD_SERVICE_NAME_LEN) == 0) && +- +-#endif +- (!gssd_have_realm_ple((void *)&kte.principal->realm)) ) { +- printerr(2, "We will use this entry (%s)\n", pname); +- ple = malloc(sizeof(struct gssd_k5_kt_princ)); +- if (ple == NULL) { +- printerr(0, "ERROR: could not allocate storage " +- "for principal list entry\n"); +-#ifdef HAVE_KRB5 +- krb5_free_unparsed_name(context, pname); +-#else +- free(pname); +-#endif +- retval = ENOMEM; +- goto out; ++ ++ /* 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; + } +- /* These will be filled in later */ +- ple->next = NULL; +- ple->ccname = NULL; +- ple->endtime = 0; +- if ((ple->realm = +-#ifdef HAVE_KRB5 +- strndup(kte.principal->realm.data, +- kte.principal->realm.length)) +-#else +- strdup(kte.principal->realm)) +-#endif +- == NULL) { +- printerr(0, "ERROR: %s while copying realm to " +- "principal list entry\n", +- "not enough memory"); +-#ifdef HAVE_KRB5 +- krb5_free_unparsed_name(context, pname); +-#else +- free(pname); +-#endif +- retval = ENOMEM; +- goto out; ++ ++ 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 ((code = krb5_copy_principal(context, +- kte.principal, &ple->princ))) { +- printerr(0, "ERROR: %s while copying principal " +- "to principal list entry\n", +- error_message(code)); +-#ifdef HAVE_KRB5 +- krb5_free_unparsed_name(context, pname); +-#else +- free(pname); +-#endif +- retval = code; +- goto out; ++ ++ if (uname(&utsbuf)) { ++ printerr(2, "mds service entry: unable to get " ++ "UTS name, skip\n"); ++ goto next; + } +- 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; ++ host = gethostbyname(utsbuf.nodename); ++ if (host == NULL) { ++ printerr(2, "mds service entry: unable to get " ++ "local hostname, skip\n"); ++ goto next; + } +- } +- else { ++ ++ 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; + } +-#ifdef HAVE_KRB5 +- krb5_free_unparsed_name(context, pname); +-#else +- free(pname); +-#endif ++ ++ /* 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))) { +@@ -695,7 +816,18 @@ + goto out; + } + +- printerr(1, "Using keytab file '%s'\n", keytabfile); ++ if (this_realm == NULL) { ++ code = krb5_get_default_realm(context, &this_realm); ++ if (code) { ++ printerr(0, "ERROR: get default realm: %s\n", ++ error_message(code)); ++ retval = code; ++ goto out; ++ } ++ printerr(1, "Local realm: %s\n", this_realm); ++ } ++ ++ 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", +@@ -710,12 +842,12 @@ + if (gssd_k5_kt_princ_list == NULL) { + printerr(0, "ERROR: No usable keytab entries found in " + "keytab '%s'\n", keytabfile); +- printerr(0, "Do you have a valid keytab entry for " +- "%s/@ in " ++ printerr(0, "You must have a valid keytab entry for " ++ "%s/@ on MDT nodes, " ++ "and %s@ on client nodes, in " + "keytab file %s ?\n", +- GSSD_SERVICE_NAME, keytabfile); +- printerr(0, "Continuing without (machine) credentials " +- "- nfs4 mounts with Kerberos will fail\n"); ++ GSSD_SERVICE_MDS, LUSTRE_ROOT_NAME, ++ keytabfile); + } + } + +@@ -865,6 +997,7 @@ + krb5_free_context(context); + } + ++#if 0 + #ifdef HAVE_SET_ALLOWABLE_ENCTYPES + /* + * this routine obtains a credentials handle via gss_acquire_cred() +@@ -920,6 +1053,7 @@ + return 0; + } + #endif /* HAVE_SET_ALLOWABLE_ENCTYPES */ ++#endif + + /* + * Obtain supported enctypes from kernel. +--- nfs-utils-1.0.10/utils/gssd/krb5_util.h.lustre 2006-08-14 10:32:04.000000000 -0600 ++++ nfs-utils-1.0.10/utils/gssd/krb5_util.h 2006-08-14 10:32:30.000000000 -0600 +@@ -10,6 +10,8 @@ + struct gssd_k5_kt_princ { + struct gssd_k5_kt_princ *next; + krb5_principal princ; ++ unsigned int fl_root:1, ++ fl_mds:1; + char *ccname; + char *realm; + krb5_timestamp endtime; +@@ -25,8 +27,32 @@ + void gssd_obtain_kernel_krb5_info(void); + + +-#ifdef HAVE_SET_ALLOWABLE_ENCTYPES +-int limit_krb5_enctypes(struct rpc_gss_sec *sec, uid_t uid); +-#endif ++#endif /* KRB5_UTIL_H */ ++#ifndef KRB5_UTIL_H ++#define KRB5_UTIL_H ++ ++#include ++ ++/* ++ * List of principals from our keytab that we ++ * may try to get credentials for ++ */ ++struct gssd_k5_kt_princ { ++ struct gssd_k5_kt_princ *next; ++ krb5_principal princ; ++ char *ccname; ++ char *realm; ++ krb5_timestamp endtime; ++}; ++ ++ ++void gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername); ++int gssd_get_krb5_machine_cred_list(char ***list); ++int gssd_refresh_krb5_machine_creds(void); ++void gssd_free_krb5_machine_cred_list(char **list); ++void gssd_setup_krb5_machine_gss_ccache(char *servername); ++void gssd_destroy_krb5_machine_creds(void); ++void gssd_obtain_kernel_krb5_info(void); ++ + + #endif /* KRB5_UTIL_H */ +--- nfs-utils-1.0.10/utils/gssd/Makefile.am.lustre 2006-08-07 00:40:50.000000000 -0600 ++++ nfs-utils-1.0.10/utils/gssd/Makefile.am 2006-08-14 10:32:30.000000000 -0600 +@@ -1,17 +1,11 @@ + ## Process this file with automake to produce Makefile.in + +-man8_MANS = gssd.man svcgssd.man +- +-RPCPREFIX = rpc. ++RPCPREFIX = + KPREFIX = @kprefix@ +-sbin_PREFIXED = gssd svcgssd +-sbin_PROGRAMS = $(sbin_PREFIXED) gss_clnt_send_err ++sbin_PREFIXED = lgssd lsvcgssd ++sbin_PROGRAMS = $(sbin_PREFIXED) + sbin_SCRIPTS = gss_destroy_creds + +-EXTRA_DIST = \ +- gss_destroy_creds \ +- $(man8_MANS) +- + COMMON_SRCS = \ + context.c \ + context_mit.c \ +@@ -20,13 +14,15 @@ + gss_util.c \ + gss_oids.c \ + err_util.c \ ++ lsupport.c \ + \ + context.h \ + err_util.h \ + gss_oids.h \ +- gss_util.h ++ gss_util.h \ ++ lsupport.h + +-gssd_SOURCES = \ ++lgssd_SOURCES = \ + $(COMMON_SRCS) \ + gssd.c \ + gssd_main_loop.c \ +@@ -37,13 +33,12 @@ + krb5_util.h \ + write_bytes.h + +-gssd_LDADD = $(RPCSECGSS_LIBS) $(KRBLIBS) +-gssd_LDFLAGS = $(KRBLDFLAGS) ++lgssd_LDADD = $(GSSAPI_LIBS) $(KRBLIBS) ++lgssd_LDFLAGS = $(KRBLDFLAGS) + +-gssd_CFLAGS = $(AM_CFLAGS) $(CFLAGS) \ +- $(RPCSECGSS_CFLAGS) $(KRBCFLAGS) ++lgssd_CFLAGS = $(AM_CFLAGS) $(CFLAGS) $(GSSAPI_CFLAGS) $(KRBCFLAGS) + +-svcgssd_SOURCES = \ ++lsvcgssd_SOURCES = \ + $(COMMON_SRCS) \ + cacheio.c \ + svcgssd.c \ +@@ -54,20 +49,11 @@ + cacheio.h \ + svcgssd.h + +-svcgssd_LDADD = \ +- ../../support/nfs/libnfs.a \ +- $(RPCSECGSS_LIBS) -lnfsidmap \ +- $(KRBLIBS) +- +-svcgssd_LDFLAGS = $(KRBLDFLAGS) +- +-svcgssd_CFLAGS = $(AM_CFLAGS) $(CFLAGS) \ +- $(RPCSECGSS_CFLAGS) $(KRBCFLAGS) ++lsvcgssd_LDADD = $(GSSAPI_LIBS) $(KRBLIBS) + +-gss_clnt_send_err_SOURCES = gss_clnt_send_err.c ++lsvcgssd_LDFLAGS = $(KRBLDFLAGS) + +-gss_clnt_send_err_CFLAGS = $(AM_CFLAGS) $(CFLAGS) \ +- $(RPCSECGSS_CFLAGS) $(KRBCFLAGS) ++lsvcgssd_CFLAGS = $(AM_CFLAGS) $(CFLAGS) $(GSSAPI_CFLAGS) $(KRBCFLAGS) + + MAINTAINERCLEANFILES = Makefile.in + +@@ -91,23 +77,3 @@ + done) + + +-# XXX This makes some assumptions about what automake does. +-# XXX But there is no install-man-hook or install-man-local. +-install-man: install-man8 install-man-links +-uninstall-man: uninstall-man8 uninstall-man-links +- +-install-man-links: +- (cd $(DESTDIR)$(man8dir) && \ +- for m in $(man8_MANS) $(dist_man8_MANS) $(nodist_man8_MANS); do \ +- inst=`echo $$m | sed -e 's/man$$/8/'`; \ +- rm -f $(RPCPREFIX)$$inst ; \ +- $(LN_S) $$inst $(RPCPREFIX)$$inst ; \ +- done) +- +-uninstall-man-links: +- (cd $(DESTDIR)$(man8dir) && \ +- for m in $(man8_MANS) $(dist_man8_MANS) $(nodist_man8_MANS); do \ +- inst=`echo $$m | sed -e 's/man$$/8/'`; \ +- rm -f $(RPCPREFIX)$$inst ; \ +- done) +- +--- nfs-utils-1.0.10/utils/gssd/svcgssd.c.lustre 2006-08-07 00:40:50.000000000 -0600 ++++ nfs-utils-1.0.10/utils/gssd/svcgssd.c 2006-08-14 10:32:45.000000000 -0600 +@@ -43,7 +43,6 @@ + #include + #include + #include +-#include + #include + #include + +@@ -54,11 +53,33 @@ + #include + #include + #include +-#include "nfslib.h" ++#include + #include "svcgssd.h" + #include "gss_util.h" + #include "err_util.h" ++#include "lsupport.h" + ++void ++closeall(int min) ++{ ++ DIR *dir = opendir("/proc/self/fd"); ++ if (dir != NULL) { ++ int dfd = dirfd(dir); ++ struct dirent *d; ++ ++ while ((d = readdir(dir)) != NULL) { ++ char *endp; ++ long n = strtol(d->d_name, &endp, 10); ++ if (*endp != '\0' && n >= min && n != dfd) ++ (void) close(n); ++ } ++ closedir(dir); ++ } else { ++ int fd = sysconf(_SC_OPEN_MAX); ++ while (--fd >= min) ++ (void) close(fd); ++ } ++} + /* + * mydaemon creates a pipe between the partent and child + * process. The parent process will wait until the +@@ -165,8 +186,8 @@ + int get_creds = 1; + int fg = 0; + int verbosity = 0; +- int rpc_verbosity = 0; + int opt; ++ int must_srv_mds = 0, must_srv_oss = 0; + extern char *optarg; + char *progname; + +@@ -181,8 +202,13 @@ + case 'v': + verbosity++; + break; +- case 'r': +- rpc_verbosity++; ++ case 'm': ++ get_creds = 1; ++ must_srv_mds = 1; ++ break; ++ case 'o': ++ get_creds = 1; ++ must_srv_oss = 1; + break; + default: + usage(argv[0]); +@@ -196,27 +222,13 @@ + progname = argv[0]; + + initerr(progname, verbosity, fg); +-#ifdef HAVE_AUTHGSS_SET_DEBUG_LEVEL +- authgss_set_debug_level(rpc_verbosity); +-#else +- if (rpc_verbosity > 0) +- printerr(0, "Warning: rpcsec_gss library does not " +- "support setting debug level\n"); +-#endif + + if (gssd_check_mechs() != 0) { + printerr(0, "ERROR: Problem with gssapi library\n"); + exit(1); + } + +- if (!fg) +- mydaemon(0, 0); +- +- signal(SIGINT, sig_die); +- signal(SIGTERM, sig_die); +- signal(SIGHUP, sig_hup); +- +- if (get_creds && !gssd_acquire_cred(GSSD_SERVICE_NAME)) { ++ if (get_creds && gssd_prepare_creds(must_srv_mds, must_srv_oss)) { + printerr(0, "unable to obtain root (machine) credentials\n"); + printerr(0, "do you have a keytab entry for " + "nfs/@ in " +@@ -225,9 +237,18 @@ + } + + if (!fg) ++ mydaemon(0, 0); ++ ++ signal(SIGINT, sig_die); ++ signal(SIGTERM, sig_die); ++ signal(SIGHUP, sig_hup); ++ ++ if (!fg) + release_parent(); + +- gssd_run(); ++ gssd_init_unique(GSSD_SVC); ++ ++ svcgssd_run(); + printerr(0, "gssd_run returned!\n"); + abort(); + } +--- nfs-utils-1.0.10/utils/gssd/svcgssd.h.lustre 2006-08-07 00:40:50.000000000 -0600 ++++ nfs-utils-1.0.10/utils/gssd/svcgssd.h 2006-08-14 10:32:30.000000000 -0600 +@@ -36,8 +36,19 @@ + #include + + void handle_nullreq(FILE *f); +-void gssd_run(void); ++void svcgssd_run(void); ++int gssd_prepare_creds(int must_srv_mds, int must_srv_oss); ++gss_cred_id_t gssd_select_svc_cred(int lustre_svc); + +-#define GSSD_SERVICE_NAME "nfs" ++extern char *mds_local_realm; ++extern char *oss_local_realm; ++ ++#define GSSD_SERVICE_NAME "lustre" ++ ++/* XXX */ ++#define GSSD_SERVICE_MDS "lustre_mds" ++#define GSSD_SERVICE_OSS "lustre_oss" ++#define LUSTRE_ROOT_NAME "lustre_root" ++#define LUSTRE_ROOT_NAMELEN 11 + + #endif /* _RPC_SVCGSSD_H_ */ +--- nfs-utils-1.0.10/utils/gssd/svcgssd_main_loop.c.lustre 2006-08-07 00:40:50.000000000 -0600 ++++ nfs-utils-1.0.10/utils/gssd/svcgssd_main_loop.c 2006-08-14 10:32:30.000000000 -0600 +@@ -47,31 +47,31 @@ + #include "err_util.h" + + void +-gssd_run() ++svcgssd_run() + { + int ret; + FILE *f; + struct pollfd pollfd; + +-#define NULLRPC_FILE "/proc/net/rpc/auth.rpcsec.init/channel" ++#define NULLRPC_FILE "/proc/net/rpc/auth.ptlrpcs.init/channel" + +- f = fopen(NULLRPC_FILE, "rw"); +- +- if (!f) { +- printerr(0, "failed to open %s: %s\n", +- NULLRPC_FILE, strerror(errno)); +- exit(1); +- } +- pollfd.fd = fileno(f); +- pollfd.events = POLLIN; + while (1) { + int save_err; + ++ while ((f = fopen(NULLRPC_FILE, "rw")) == NULL) { ++ printerr(3, "failed to open %s: %s\n", ++ NULLRPC_FILE, strerror(errno)); ++ sleep(1); ++ } ++ pollfd.fd = fileno(f); ++ pollfd.events = POLLIN; ++ + pollfd.revents = 0; +- printerr(1, "entering poll\n"); +- ret = poll(&pollfd, 1, -1); ++ printerr(3, "entering poll\n"); ++ ret = poll(&pollfd, 1, 1000); + save_err = errno; +- printerr(1, "leaving poll\n"); ++ printerr(3, "leaving poll\n"); ++ + if (ret < 0) { + if (save_err != EINTR) + printerr(0, "error return from poll: %s\n", +@@ -87,5 +87,6 @@ + if (pollfd.revents & POLLIN) + handle_nullreq(f); + } ++ fclose(f); + } + } +--- nfs-utils-1.0.10/utils/gssd/svcgssd_proc.c.lustre 2006-08-07 00:40:50.000000000 -0600 ++++ nfs-utils-1.0.10/utils/gssd/svcgssd_proc.c 2006-08-14 10:32:30.000000000 -0600 +@@ -35,7 +35,6 @@ + + #include + #include +-#include + + #include + #include +@@ -44,25 +43,28 @@ + #include + #include + #include +-#include ++#include + + #include "svcgssd.h" + #include "gss_util.h" + #include "err_util.h" + #include "context.h" + #include "cacheio.h" ++#include "lsupport.h" + + extern char * mech2file(gss_OID mech); +-#define SVCGSSD_CONTEXT_CHANNEL "/proc/net/rpc/auth.rpcsec.context/channel" +-#define SVCGSSD_INIT_CHANNEL "/proc/net/rpc/auth.rpcsec.init/channel" ++#define SVCGSSD_CONTEXT_CHANNEL "/proc/net/rpc/auth.ptlrpcs.context/channel" ++#define SVCGSSD_INIT_CHANNEL "/proc/net/rpc/auth.ptlrpcs.init/channel" + + #define TOKEN_BUF_SIZE 8192 + + struct svc_cred { +- uid_t cr_uid; +- gid_t cr_gid; +- int cr_ngroups; +- gid_t cr_groups[NGROUPS]; ++ uint32_t cr_remote; ++ uint32_t cr_usr_root; ++ uint32_t cr_usr_mds; ++ uid_t cr_uid; ++ uid_t cr_mapped_uid; ++ uid_t cr_gid; + }; + + static int +@@ -70,10 +72,9 @@ + gss_OID mech, gss_buffer_desc *context_token) + { + FILE *f; +- int i; + char *fname = NULL; + +- printerr(1, "doing downcall\n"); ++ printerr(2, "doing downcall\n"); + if ((fname = mech2file(mech)) == NULL) + goto out_err; + f = fopen(SVCGSSD_CONTEXT_CHANNEL, "w"); +@@ -86,11 +87,12 @@ + qword_printhex(f, out_handle->value, out_handle->length); + /* XXX are types OK for the rest of this? */ + qword_printint(f, 0x7fffffff); /*XXX need a better timeout */ ++ qword_printint(f, cred->cr_remote); ++ qword_printint(f, cred->cr_usr_root); ++ qword_printint(f, cred->cr_usr_mds); ++ qword_printint(f, cred->cr_mapped_uid); + qword_printint(f, cred->cr_uid); + qword_printint(f, cred->cr_gid); +- qword_printint(f, cred->cr_ngroups); +- for (i=0; i < cred->cr_ngroups; i++) +- qword_printint(f, cred->cr_groups[i]); + qword_print(f, fname); + qword_printhex(f, context_token->value, context_token->length); + qword_eol(f); +@@ -119,7 +121,7 @@ + /* XXXARG: */ + int g; + +- printerr(1, "sending null reply\n"); ++ printerr(2, "sending null reply\n"); + + qword_addhex(&bp, &blen, in_handle->value, in_handle->length); + qword_addhex(&bp, &blen, in_token->value, in_token->length); +@@ -159,6 +161,7 @@ + #define rpcsec_gsserr_credproblem 13 + #define rpcsec_gsserr_ctxproblem 14 + ++#if 0 + static void + add_supplementary_groups(char *secname, char *name, struct svc_cred *cred) + { +@@ -182,7 +185,9 @@ + } + } + } ++#endif + ++#if 0 + static int + get_ids(gss_name_t client_name, gss_OID mech, struct svc_cred *cred) + { +@@ -248,7 +253,9 @@ + out: + return res; + } ++#endif + ++#if 0 + void + print_hexl(int pri, unsigned char *cp, int length) + { +@@ -285,12 +292,114 @@ + printerr(pri,"\n"); + } + } ++#endif ++ ++static int ++get_ids(gss_name_t client_name, gss_OID mech, struct svc_cred *cred, ++ ptl_nid_t ptl_nid) ++{ ++ u_int32_t maj_stat, min_stat; ++ gss_buffer_desc name; ++ char *sname, *realm, *slash; ++ int res = -1; ++ gss_OID name_type = GSS_C_NO_OID; ++ struct passwd *pw; ++ ++ memset(cred, 0, sizeof(*cred)); ++ ++ maj_stat = gss_display_name(&min_stat, client_name, &name, &name_type); ++ if (maj_stat != GSS_S_COMPLETE) { ++ pgsserr("get_ids: gss_display_name", ++ maj_stat, min_stat, mech); ++ return -1; ++ } ++ if (name.length >= 0xffff || /* be certain name.length+1 doesn't overflow */ ++ !(sname = calloc(name.length + 1, 1))) { ++ printerr(0, "WARNING: get_ids: error allocating %d bytes " ++ "for sname\n", name.length + 1); ++ gss_release_buffer(&min_stat, &name); ++ return -1; ++ } ++ memcpy(sname, name.value, name.length); ++ printerr(1, "authenticate user %s\n", sname); ++ gss_release_buffer(&min_stat, &name); ++ ++#if 0 ++ lookup_mapping(sname, ptl_nal, ptl_netid, ptl_nid, &cred->cr_mapped_uid); ++#else ++ cred->cr_mapped_uid = -1; ++#endif ++ ++ realm = strchr(sname, '@'); ++ if (!realm) { ++ printerr(0, "WARNNING: principal %s contains no realm name\n", ++ sname); ++ cred->cr_remote = (mds_local_realm != NULL); ++ } else { ++ *realm++ = '\0'; ++ if (!mds_local_realm) ++ cred->cr_remote = 1; ++ else ++ cred->cr_remote = ++ (strcasecmp(mds_local_realm, realm) != 0); ++ } ++ ++ if (cred->cr_remote) { ++ if (cred->cr_mapped_uid != -1) ++ res = 0; ++ else ++ printerr(0, "principal %s is remote without mapping\n", ++ sname); ++ goto out_free; ++ } ++ ++ slash = strchr(sname, '/'); ++ if (slash) ++ *slash = '\0'; ++ ++ if (!(pw = getpwnam(sname))) { ++ /* If client use machine credential, we map it to root, which ++ * will subject to further mapping by root-squash in kernel. ++ * ++ * MDS service keytab is treated as special user, also mapped ++ * to root. OSS service keytab can't be used as a user. ++ */ ++ if (!strcmp(sname, LUSTRE_ROOT_NAME)) { ++ printerr(2, "lustre_root principal, resolve to uid 0\n"); ++ cred->cr_uid = 0; ++ cred->cr_usr_root = 1; ++ } else if (!strcmp(sname, GSSD_SERVICE_MDS)) { ++ printerr(2, "mds service principal, resolve to uid 0\n"); ++ cred->cr_uid = 0; ++ cred->cr_usr_mds = 1; ++ } else { ++ cred->cr_uid = -1; ++ if (cred->cr_mapped_uid == -1) { ++ printerr(0, "invalid user %s\n", sname); ++ goto out_free; ++ } ++ printerr(2, "user %s mapped to %u\n", ++ sname, cred->cr_mapped_uid); ++ } ++ } else { ++ cred->cr_uid = pw->pw_uid; ++ printerr(2, "%s resolve to uid %u\n", sname, cred->cr_uid); ++ } ++ ++ res = 0; ++out_free: ++ free(sname); ++ return res; ++} ++ ++typedef struct gss_union_ctx_id_t { ++ gss_OID mech_type; ++ gss_ctx_id_t internal_ctx_id; ++} gss_union_ctx_id_desc, *gss_union_ctx_id_t; + + void + handle_nullreq(FILE *f) { +- /* XXX initialize to a random integer to reduce chances of unnecessary +- * invalidation of existing ctx's on restarting svcgssd. */ +- static u_int32_t handle_seq = 0; ++ uint64_t handle_seq; + char in_tok_buf[TOKEN_BUF_SIZE]; + char in_handle_buf[15]; + char out_handle_buf[15]; +@@ -302,10 +411,13 @@ + ignore_out_tok = {.value = NULL}, + /* XXX isn't there a define for this?: */ + null_token = {.value = NULL}; ++ uint32_t lustre_svc; ++ uint64_t ptl_nid; + u_int32_t ret_flags; + gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; + gss_name_t client_name; + gss_OID mech = GSS_C_NO_OID; ++ gss_cred_id_t svc_cred; + u_int32_t maj_stat = GSS_S_FAILURE, min_stat = 0; + u_int32_t ignore_min_stat; + struct svc_cred cred; +@@ -313,7 +425,7 @@ + static int lbuflen = 0; + static char *cp; + +- printerr(1, "handling null request\n"); ++ printerr(2, "handling null request\n"); + + if (readline(fileno(f), &lbuf, &lbuflen) != 1) { + printerr(0, "WARNING: handle_nullreq: " +@@ -323,15 +435,21 @@ + + cp = lbuf; + ++ qword_get(&cp, (char *) &lustre_svc, sizeof(lustre_svc)); ++ qword_get(&cp, (char *) &ptl_nid, sizeof(ptl_nid)); ++ qword_get(&cp, (char *) &handle_seq, sizeof(handle_seq)); ++ printerr(1, "handling req: svc %u, nid %016llx, idx %llx\n", ++ lustre_svc, ptl_nid, handle_seq); ++ + in_handle.length = (size_t) qword_get(&cp, in_handle.value, + sizeof(in_handle_buf)); +- printerr(2, "in_handle: \n"); +- print_hexl(2, in_handle.value, in_handle.length); ++ printerr(3, "in_handle: \n"); ++ print_hexl(3, in_handle.value, in_handle.length); + + in_tok.length = (size_t) qword_get(&cp, in_tok.value, + sizeof(in_tok_buf)); +- printerr(2, "in_tok: \n"); +- print_hexl(2, in_tok.value, in_tok.length); ++ printerr(3, "in_tok: \n"); ++ print_hexl(3, in_tok.value, in_tok.length); + + if (in_tok.length < 0) { + printerr(0, "WARNING: handle_nullreq: " +@@ -351,7 +469,13 @@ + memcpy(&ctx, in_handle.value, in_handle.length); + } + +- maj_stat = gss_accept_sec_context(&min_stat, &ctx, gssd_creds, ++ svc_cred = gssd_select_svc_cred(lustre_svc); ++ if (!svc_cred) { ++ printerr(0, "no service credential for svc %u\n", lustre_svc); ++ goto out_err; ++ } ++ ++ maj_stat = gss_accept_sec_context(&min_stat, &ctx, svc_cred, + &in_tok, GSS_C_NO_CHANNEL_BINDINGS, &client_name, + &mech, &out_tok, &ret_flags, NULL, NULL); + +@@ -369,7 +493,7 @@ + maj_stat, min_stat, mech); + goto out_err; + } +- if (get_ids(client_name, mech, &cred)) { ++ if (get_ids(client_name, mech, &cred, ptl_nid)) { + /* get_ids() prints error msg */ + maj_stat = GSS_S_BAD_NAME; /* XXX ? */ + gss_release_name(&ignore_min_stat, &client_name); +@@ -377,10 +501,8 @@ + } + gss_release_name(&ignore_min_stat, &client_name); + +- + /* Context complete. Pass handle_seq in out_handle to use + * for context lookup in the kernel. */ +- handle_seq++; + out_handle.length = sizeof(handle_seq); + memcpy(out_handle.value, &handle_seq, sizeof(handle_seq)); + +@@ -404,7 +526,6 @@ + free(ctx_token.value); + if (out_tok.value != NULL) + gss_release_buffer(&ignore_min_stat, &out_tok); +- printerr(1, "finished handling null request\n"); + return; + + out_err: +--- nfs-utils-1.0.10/utils/gssd/lsupport.c.lustre 2006-08-14 10:32:30.000000000 -0600 ++++ nfs-utils-1.0.10/utils/gssd/lsupport.c 2006-08-14 10:32:30.000000000 -0600 +@@ -0,0 +1,604 @@ ++/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*- ++ * vim:expandtab:shiftwidth=8:tabstop=8: ++ * ++ * Copyright (c) 2005 Cluster File Systems, Inc. ++ * ++ * This file is part of Lustre, http://www.lustre.org. ++ * ++ * Lustre is free software; you can redistribute it and/or ++ * modify it under the terms of version 2 of the GNU General Public ++ * License as published by the Free Software Foundation. ++ * ++ * Lustre is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with Lustre; if not, write to the Free Software ++ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++ */ ++ ++#ifndef _GNU_SOURCE ++#define _GNU_SOURCE ++#endif ++#include "config.h" ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "err_util.h" ++#include "gssd.h" ++#include "lsupport.h" ++ ++/**************************************** ++ * exclusive startup * ++ ****************************************/ ++ ++static struct __sem_s { ++ char *name; ++ key_t sem_key; ++ int sem_id; ++} sems[2] = { ++ [GSSD_CLI] = { "client", 0x3a92d473, 0 }, ++ [GSSD_SVC] = { "server", 0x3b92d473, 0 }, ++}; ++ ++void gssd_init_unique(int type) ++{ ++ struct __sem_s *sem = &sems[type]; ++ struct sembuf sembuf; ++ ++ assert(type == GSSD_CLI || type == GSSD_SVC); ++ ++again: ++ sem->sem_id = semget(sem->sem_key, 1, IPC_CREAT | IPC_EXCL | 0700); ++ if (sem->sem_id == -1) { ++ if (errno != EEXIST) { ++ printerr(0, "Create sem: %s\n", strerror(errno)); ++ exit(-1); ++ } ++ ++ /* already exist. Note there's still a small window racing ++ * with other processes, due to the stupid semaphore semantics. ++ */ ++ sem->sem_id = semget(sem->sem_key, 0, 0700); ++ if (sem->sem_id == -1) { ++ if (errno == ENOENT) { ++ printerr(0, "another instance just exit, " ++ "try again\n"); ++ goto again; ++ } ++ ++ printerr(0, "Obtain sem: %s\n", strerror(errno)); ++ exit(-1); ++ } ++ } else { ++ int val = 1; ++ ++ if (semctl(sem->sem_id, 0, SETVAL, val) == -1) { ++ printerr(0, "Initialize sem: %s\n", ++ strerror(errno)); ++ exit(-1); ++ } ++ } ++ ++ sembuf.sem_num = 0; ++ sembuf.sem_op = -1; ++ sembuf.sem_flg = IPC_NOWAIT | SEM_UNDO; ++ ++ if (semop(sem->sem_id, &sembuf, 1) != 0) { ++ if (errno == EAGAIN) { ++ printerr(0, "Another instance is running, exit\n"); ++ exit(0); ++ } ++ printerr(0, "Grab sem: %s\n", strerror(errno)); ++ exit(0); ++ } ++ ++ printerr(2, "Successfully created %s global identity\n", sem->name); ++} ++ ++void gssd_exit_unique(int type) ++{ ++ assert(type == GSSD_CLI || type == GSSD_SVC); ++ ++ /* ++ * do nothing. we can't remove the sem here, otherwise the race ++ * window would be much bigger. So it's sad we have to leave the ++ * sem in the system forever. ++ */ ++} ++ ++/**************************************** ++ * client side resolvation: * ++ * nal/netid/nid => hostname * ++ ****************************************/ ++ ++char gethostname_ex[PATH_MAX] = GSSD_DEFAULT_GETHOSTNAME_EX; ++ ++typedef int ptl_nid2hostname_t(char *nal, uint32_t net, uint32_t addr, ++ char *buf, int buflen); ++ ++/* FIXME what about IPv6? */ ++static ++int socknal_nid2hostname(char *nal, uint32_t net, uint32_t addr, ++ char *buf, int buflen) ++{ ++ struct hostent *ent; ++ ++ addr = htonl(addr); ++ ent = gethostbyaddr(&addr, sizeof(addr), AF_INET); ++ if (!ent) { ++ printerr(0, "%s: can't resolve 0x%x\n", nal, addr); ++ return -1; ++ } ++ if (strlen(ent->h_name) >= buflen) { ++ printerr(0, "%s: name too long: %s\n", nal, ent->h_name); ++ return -1; ++ } ++ strcpy(buf, ent->h_name); ++ ++ printerr(2, "%s: net 0x%x, addr 0x%x => %s\n", ++ nal, net, addr, buf); ++ return 0; ++} ++ ++static ++int lonal_nid2hostname(char *nal, uint32_t net, uint32_t addr, ++ char *buf, int buflen) ++{ ++ struct utsname uts; ++ struct hostent *ent; ++ ++ if (addr) { ++ printerr(0, "%s: addr is 0x%x, we expect 0\n", nal, addr); ++ return -1; ++ } ++ ++ if (uname(&uts)) { ++ printerr(0, "%s: failed obtain local machine name\n", nal); ++ return -1; ++ } ++ ++ ent = gethostbyname(uts.nodename); ++ if (!ent) { ++ printerr(0, "%s: failed obtain canonical name of %s\n", ++ nal, uts.nodename); ++ return -1; ++ } ++ ++ if (strlen(ent->h_name) >= buflen) { ++ printerr(0, "%s: name too long: %s\n", nal, ent->h_name); ++ return -1; ++ } ++ strcpy(buf, ent->h_name); ++ ++ printerr(2, "%s: addr 0x%x => %s\n", nal, addr, buf); ++ return 0; ++} ++ ++static int is_space(char c) ++{ ++ return (c == ' ' || c == '\t' || c == '\n'); ++} ++ ++static ++int external_nid2hostname(char *nal, uint32_t net, uint32_t addr, ++ char *namebuf, int namebuflen) ++{ ++ const int bufsize = PATH_MAX + 256; ++ char buf[bufsize], *head, *tail; ++ FILE *fghn; ++ ++ sprintf(buf, "%s %s 0x%x 0x%x", gethostname_ex, nal, net, addr); ++ printerr(2, "cmd: %s\n", buf); ++ ++ fghn = popen(buf, "r"); ++ if (fghn == NULL) { ++ printerr(0, "failed to call %s\n", gethostname_ex); ++ return -1; ++ } ++ ++ head = fgets(buf, bufsize, fghn); ++ if (head == NULL) { ++ printerr(0, "can't read\n"); ++ return -1; ++ } ++ if (pclose(fghn) == -1) ++ printerr(1, "pclose failed, continue\n"); ++ ++ /* trim head/tail space */ ++ while (is_space(*head)) ++ head++; ++ ++ tail = head + strlen(head); ++ if (tail <= head) { ++ printerr(0, "no output\n"); ++ return -1; ++ } ++ while (is_space(*(tail - 1))) ++ tail--; ++ if (tail <= head) { ++ printerr(0, "output are all space\n"); ++ return -1; ++ } ++ *tail = '\0'; ++ ++ /* start with '@' means error msg */ ++ if (head[0] == '@') { ++ printerr(0, "%s\n", &head[1]); ++ return -1; ++ } ++ ++ if (tail - head > namebuflen) { ++ printerr(0, "hostname too long: %s\n", head); ++ return -1; ++ } ++ ++ printerr(2, "%s: net 0x%x, addr 0x%x => %s\n", ++ nal, net, addr, head); ++ strcpy(namebuf, head); ++ return 0; ++} ++ ++enum { ++ QSWNAL = 1, ++ SOCKNAL = 2, ++ GMNAL = 3, ++ /* 4 unused */ ++ TCPNAL = 5, ++ ROUTER = 6, ++ OPENIBNAL = 7, ++ IIBNAL = 8, ++ LONAL = 9, ++ RANAL = 10, ++ VIBNAL = 11, ++ NAL_ENUM_END_MARKER ++}; ++ ++static struct { ++ char *name; ++ ptl_nid2hostname_t *nid2name; ++} converter[NAL_ENUM_END_MARKER] = { ++ {"UNUSED0", NULL}, ++ {"QSWNAL", external_nid2hostname}, ++ {"SOCKNAL", socknal_nid2hostname}, ++ {"GMNAL", external_nid2hostname}, ++ {"UNUSED4", NULL}, ++ {"TCPNAL", NULL}, ++ {"ROUTER", NULL}, ++ {"OPENIBNAL", external_nid2hostname}, ++ {"IIBNAL", external_nid2hostname}, ++ {"LONAL", lonal_nid2hostname}, ++ {"RANAL", NULL}, ++ {"VIBNAL", external_nid2hostname}, ++}; ++ ++int ptl_nid2hostname(uint64_t nid, char *buf, int buflen) ++{ ++ uint32_t nal, net, addr; ++ ++ addr = LNET_NIDADDR(nid); ++ net = LNET_NIDNET(nid); ++ nal = LNET_NETTYP(net); ++ ++ if (nal >= NAL_ENUM_END_MARKER) { ++ printerr(0, "ERROR: Unrecognized NAL %u\n", nal); ++ return -1; ++ } ++ ++ if (converter[nal].nid2name == NULL) { ++ printerr(0, "ERROR: NAL %s converter not ready\n", ++ converter[nal].name); ++ return -1; ++ } ++ ++ return converter[nal].nid2name(converter[nal].name, net, addr, ++ buf, buflen); ++} ++ ++ ++/**************************************** ++ * portals support routine * ++ ****************************************/ ++ ++static struct hostent * ++ptl_gethostbyname(char * hname) { ++ struct hostent *he; ++ ++ he = gethostbyname(hname); ++ if (!he) { ++ switch(h_errno) { ++ case HOST_NOT_FOUND: ++ case NO_ADDRESS: ++ printerr(0, "Unable to resolve hostname: %s\n", ++ hname); ++ break; ++ default: ++ printerr(0, "gethostbyname %s: %s\n", ++ hname, strerror(h_errno)); ++ break; ++ } ++ return NULL; ++ } ++ return he; ++} ++ ++int ++ptl_parse_ipquad (uint32_t *ipaddrp, char *str) ++{ ++ int a; ++ int b; ++ int c; ++ int d; ++ ++ if (sscanf(str, "%d.%d.%d.%d", &a, &b, &c, &d) == 4 && ++ (a & ~0xff) == 0 && (b & ~0xff) == 0 && ++ (c & ~0xff) == 0 && (d & ~0xff) == 0) ++ { ++ *ipaddrp = (a<<24)|(b<<16)|(c<<8)|d; ++ return (0); ++ } ++ ++ return (-1); ++} ++ ++int ++ptl_parse_ipaddr (uint32_t *ipaddrp, char *str) ++{ ++ struct hostent *he; ++ ++ if (!strcmp (str, "_all_")) { ++ *ipaddrp = 0; ++ return (0); ++ } ++ ++ if (ptl_parse_ipquad(ipaddrp, str) == 0) ++ return (0); ++ ++ if ((('a' <= str[0] && str[0] <= 'z') || ++ ('A' <= str[0] && str[0] <= 'Z')) && ++ (he = ptl_gethostbyname (str)) != NULL) { ++ uint32_t addr = *(uint32_t *)he->h_addr; ++ ++ *ipaddrp = ntohl(addr); /* HOST byte order */ ++ return (0); ++ } ++ ++ return (-1); ++} ++ ++int ++ptl_parse_nid (ptl_nid_t *nidp, char *str) ++{ ++ uint32_t ipaddr; ++ char *end; ++ unsigned long long ullval; ++ ++ if (ptl_parse_ipaddr (&ipaddr, str) == 0) { ++#if !CRAY_PORTALS ++ *nidp = (ptl_nid_t)ipaddr; ++#else ++ *nidp = (((ptl_nid_t)ipaddr & PNAL_HOSTID_MASK) << PNAL_VNODE_SHIFT); ++#endif ++ return (0); ++ } ++ ++ ullval = strtoull(str, &end, 0); ++ if (end != str && *end == 0) { ++ /* parsed whole non-empty string */ ++ *nidp = (ptl_nid_t)ullval; ++ return (0); ++ } ++ ++ return (-1); ++} ++ ++ ++/**************************************** ++ * user mapping database handling * ++ * (very rudiment) * ++ ****************************************/ ++ ++#define MAPPING_GROW_SIZE 512 ++#define MAX_LINE_LEN 1024 ++ ++struct user_map_item { ++ char *principal; /* NULL means match all */ ++ ptl_netid_t netid; ++ ptl_nid_t nid; ++ uid_t uid; ++}; ++ ++struct user_mapping { ++ int size; ++ int nitems; ++ struct user_map_item *items; ++}; ++ ++static struct user_mapping mapping = {0, 0, NULL}; ++/* FIXME to be finished: monitor change of mapping database */ ++static int mapping_changed = 1; ++ ++static ++void cleanup_mapping(void) ++{ ++ int n; ++ ++ for (n = 0; n < mapping.nitems; n++) { ++ assert(mapping.items[n].principal); ++ free(mapping.items[n].principal); ++ } ++ mapping.nitems = 0; ++} ++ ++static ++int grow_mapping(int size) ++{ ++ struct user_map_item *new; ++ int newsize; ++ ++ if (size <= mapping.size) ++ return 0; ++ ++ newsize = mapping.size + MAPPING_GROW_SIZE; ++ while (newsize < size) ++ newsize += MAPPING_GROW_SIZE; ++ ++ new = malloc(newsize * sizeof(struct user_map_item)); ++ if (!new) { ++ printerr(0, "can't alloc mapping size %d\n", newsize); ++ return -1; ++ } ++ memcpy(new, mapping.items, mapping.nitems * sizeof(void*)); ++ free(mapping.items); ++ mapping.items = new; ++ mapping.size = newsize; ++ return 0; ++} ++ ++uid_t parse_uid(char *uidstr) ++{ ++ struct passwd *pw; ++ char *p = NULL; ++ long uid; ++ ++ pw = getpwnam(uidstr); ++ if (pw) ++ return pw->pw_uid; ++ ++ uid = strtol(uidstr, &p, 0); ++ if (*p == '\0') ++ return (uid_t) uid; ++ ++ return -1; ++} ++ ++static ++int read_mapping_db(void) ++{ ++ char princ[MAX_LINE_LEN]; ++ char nid_str[MAX_LINE_LEN]; ++ char dest[MAX_LINE_LEN]; ++ ptl_nid_t ptl_nid; ++ uid_t dest_uid; ++ FILE *f; ++ char *line, linebuf[MAX_LINE_LEN]; ++ ++ /* cleanup old mappings */ ++ cleanup_mapping(); ++ ++ f = fopen(MAPPING_DATABASE_FILE, "r"); ++ if (!f) { ++ printerr(0, "can't open mapping database: %s\n", ++ MAPPING_DATABASE_FILE); ++ return -1; ++ } ++ ++ while ((line = fgets(linebuf, MAX_LINE_LEN, f))) { ++ char *name; ++ ++ if (strlen(line) >= MAX_LINE_LEN) { ++ printerr(0, "invalid mapping db: line too long (%d)\n", ++ strlen(line)); ++ cleanup_mapping(); ++ fclose(f); ++ return -1; ++ } ++ if (sscanf(line, "%s %s %s", princ, nid_str, dest) != 3) { ++ printerr(0, "mapping db: syntax error\n"); ++ cleanup_mapping(); ++ fclose(f); ++ return -1; ++ } ++ if (grow_mapping(mapping.nitems + 1)) { ++ printerr(0, "fail to grow mapping to %d\n", ++ mapping.nitems + 1); ++ fclose(f); ++ return -1; ++ } ++ if (!strcmp(princ, "*")) { ++ name = NULL; ++ } else { ++ name = strdup(princ); ++ if (!name) { ++ printerr(0, "fail to dup str %s\n", princ); ++ fclose(f); ++ return -1; ++ } ++ } ++ if (ptl_parse_nid(&ptl_nid, nid_str)) { ++ printerr(0, "fail to parse nid %s\n", nid_str); ++ fclose(f); ++ return -1; ++ } ++ dest_uid = parse_uid(dest); ++ if (dest_uid == -1) { ++ printerr(0, "no valid user: %s\n", dest); ++ free(name); ++ fclose(f); ++ return -1; ++ } ++ ++ mapping.items[mapping.nitems].principal = name; ++ mapping.items[mapping.nitems].netid = 0; ++ mapping.items[mapping.nitems].nid = ptl_nid; ++ mapping.items[mapping.nitems].uid = dest_uid; ++ mapping.nitems++; ++ printerr(1, "add mapping: %s(%s/0x%llx) ==> %d\n", ++ name ? name : "*", nid_str, ptl_nid, dest_uid); ++ } ++ ++ return 0; ++} ++ ++int lookup_mapping(char *princ, uint32_t nal, ptl_netid_t netid, ++ ptl_nid_t nid, uid_t *uid) ++{ ++ int n; ++ ++ /* FIXME race condition here */ ++ if (mapping_changed) { ++ if (read_mapping_db()) ++ printerr(0, "all remote users will be denied\n"); ++ mapping_changed = 0; ++ } ++ ++ for (n = 0; n < mapping.nitems; n++) { ++ struct user_map_item *entry = &mapping.items[n]; ++ ++ if (entry->netid != netid || entry->nid != nid) ++ continue; ++ if (!entry->principal || ++ !strcasecmp(entry->principal, princ)) { ++ printerr(1, "found mapping: %s ==> %d\n", ++ princ, entry->uid); ++ *uid = entry->uid; ++ return 0; ++ } ++ } ++ printerr(1, "no mapping for %s\n", princ); ++ *uid = -1; ++ return -1; ++} ++ +--- nfs-utils-1.0.10/utils/gssd/lsupport.h.lustre 2006-08-14 10:32:30.000000000 -0600 ++++ nfs-utils-1.0.10/utils/gssd/lsupport.h 2006-08-14 10:32:30.000000000 -0600 +@@ -0,0 +1,68 @@ ++/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*- ++ * vim:expandtab:shiftwidth=8:tabstop=8: ++ */ ++ ++#ifndef __LIBCFS_H__ ++#define __LIBCFS_H__ ++ ++#include ++#include ++ ++#define GSSD_CLI (0) ++#define GSSD_SVC (1) ++ ++void gssd_init_unique(int type); ++void gssd_exit_unique(int type); ++ ++/* ++ * copied from lustre source ++ */ ++ ++typedef uint64_t ptl_nid_t; ++typedef uint32_t ptl_netid_t; ++ ++#define LUSTRE_GSS_SVC_MDS 0 ++#define LUSTRE_GSS_SVC_OSS 1 ++ ++struct lgssd_upcall_data { ++ uint32_t seq; ++ uint32_t uid; ++ uint32_t gid; ++ uint32_t svc; ++ uint64_t nid; ++ char obd[64]; ++}; ++ ++#define GSSD_INTERFACE_VERSION (1) ++ ++struct lgssd_ioctl_param { ++ int version; /* in */ ++ char *uuid; /* in */ ++ int lustre_svc; /* in */ ++ uid_t uid; /* in */ ++ gid_t gid; /* in */ ++ long send_token_size;/* in */ ++ char *send_token; /* in */ ++ long reply_buf_size; /* in */ ++ char *reply_buf; /* in */ ++ long status; /* out */ ++ long reply_length; /* out */ ++}; ++ ++#define GSSD_DEFAULT_GETHOSTNAME_EX "/etc/lustre/nid2hostname" ++#define MAPPING_DATABASE_FILE "/etc/lustre/idmap.conf" ++ ++int ptl_nid2hostname(uint64_t nid, char *buf, int buflen); ++int lookup_mapping(char *princ, uint32_t nal, ptl_netid_t netid, ++ ptl_nid_t nid, uid_t *uid); ++ ++/* how an LNET NID encodes net:address */ ++#define LNET_NIDADDR(nid) ((uint32_t)((nid) & 0xffffffff)) ++#define LNET_NIDNET(nid) ((uint32_t)(((nid) >> 32)) & 0xffffffff) ++#define LNET_MKNID(net,addr) ((((uint64_t)(net))<<32)|((uint64_t)(addr))) ++/* how net encodes type:number */ ++#define LNET_NETNUM(net) ((net) & 0xffff) ++#define LNET_NETTYP(net) (((net) >> 16) & 0xffff) ++#define LNET_MKNET(typ,num) ((((uint32_t)(typ))<<16)|((uint32_t)(num))) ++ ++#endif /* __LIBCFS_H__ */ +--- nfs-utils-1.0.10/utils/gssd/cacheio.c.lustre 2006-08-07 00:40:50.000000000 -0600 ++++ nfs-utils-1.0.10/utils/gssd/cacheio.c 2006-08-14 10:32:30.000000000 -0600 +@@ -227,7 +227,8 @@ + return -1; + while (*bp == ' ') bp++; + *bpp = bp; +- *dest = '\0'; ++// why should we clear *dest??? ++// *dest = '\0'; + return len; + } + +--- nfs-utils-1.0.10/utils/Makefile.am.lustre 2006-08-07 00:40:50.000000000 -0600 ++++ nfs-utils-1.0.10/utils/Makefile.am 2006-08-14 10:32:30.000000000 -0600 +@@ -2,31 +2,6 @@ + + OPTDIRS = + +-if CONFIG_RQUOTAD +-OPTDIRS += rquotad +-endif +- +-if CONFIG_NFSV4 +-OPTDIRS += idmapd +-endif +- +-if CONFIG_GSS +-OPTDIRS += gssd +-endif +- +-if CONFIG_MOUNT +-OPTDIRS += mount +-endif +- +-SUBDIRS = \ +- exportfs \ +- lockd \ +- mountd \ +- nfsd \ +- nfsstat \ +- nhfsstone \ +- showmount \ +- statd \ +- $(OPTDIRS) ++SUBDIRS = gssd + + MAINTAINERCLEANFILES = Makefile.in +--- nfs-utils-1.0.10/configure.in.lustre 2006-08-07 00:40:50.000000000 -0600 ++++ nfs-utils-1.0.10/configure.in 2006-08-14 10:32:30.000000000 -0600 +@@ -17,61 +17,14 @@ + RELEASE=$withval, + RELEASE=1) + AC_SUBST(RELEASE) +-AC_ARG_WITH(statedir, +- [ --with-statedir=/foo use state dir /foo [/var/lib/nfs]], +- statedir=$withval, +- statedir=/var/lib/nfs) +- AC_SUBST(statedir) +-AC_ARG_WITH(statduser, +- [AC_HELP_STRING([--with-statduser=rpcuser], +- [statd to run under @<:@rpcuser or nobody@:>@] +- )], +- statduser=$withval, +- if test "x$cross_compiling" = "xno"; then +- if grep -s '^rpcuser:' /etc/passwd > /dev/null; then +- statduser=rpcuser +- else +- statduser=nobody +- fi +- else +- statduser=nobody +- fi) +- AC_SUBST(statduser) +-AC_ARG_ENABLE(nfsv3, +- [AC_HELP_STRING([--enable-nfsv3], +- [enable support for NFSv3 @<:@default=yes@:>@])], +- enable_nfsv3=$enableval, +- enable_nfsv3=yes) +- if test "$enable_nfsv3" = yes; then +- AC_DEFINE(NFS3_SUPPORTED, 1, [Define this if you want NFSv3 support compiled in]) +- else +- enable_nfsv3= +- fi +- AC_SUBST(enable_nfsv3) +-AC_ARG_ENABLE(nfsv4, +- [AC_HELP_STRING([--enable-nfsv4], +- [enable support for NFSv4 @<:@default=yes@:>@])], +- enable_nfsv4=$enableval, +- enable_nfsv4=yes) +- if test "$enable_nfsv4" = yes; then +- AC_DEFINE(NFS4_SUPPORTED, 1, [Define this if you want NFSv4 support compiled in]) +- IDMAPD=idmapd +- else +- enable_nfsv4= +- IDMAPD= +- fi +- AC_SUBST(IDMAPD) +- AC_SUBST(enable_nfsv4) +- AM_CONDITIONAL(CONFIG_NFSV4, [test "$enable_nfsv4" = "yes"]) + AC_ARG_ENABLE(gss, + [AC_HELP_STRING([--enable-gss], + [enable support for rpcsec_gss @<:@default=yes@:>@])], + enable_gss=$enableval, + enable_gss=yes) + if test "$enable_gss" = yes; then +- AC_DEFINE(GSS_SUPPORTED, 1, [Define this if you want rpcsec_gss support compiled in]) +- GSSD=gssd +- SVCGSSD=svcgssd ++ GSSD=lgssd ++ SVCGSSD=lsvcgssd + else + enable_gss= + GSSD= +@@ -81,38 +34,6 @@ + AC_SUBST(SVCGSSD) + AC_SUBST(enable_gss) + AM_CONDITIONAL(CONFIG_GSS, [test "$enable_gss" = "yes"]) +-AC_ARG_ENABLE(kprefix, +- [AC_HELP_STRING([--enable-kprefix], [install progs as rpc.knfsd etc])], +- test "$enableval" = "yes" && kprefix=k, +- kprefix=) +- AC_SUBST(kprefix) +-AC_ARG_ENABLE(secure-statd, +- [AC_HELP_STRING([--enable-secure-statd], +- [Only lockd can use statd (security)])], +- test "$enableval" = "yes" && secure_statd=yes, +- secure_statd=no) +- if test "$secure_statd" = yes; then +- AC_DEFINE(RESTRICTED_STATD, 1, [Define this if you want to enable various security checks in statd. These checks basically keep anyone but lockd from using this service.]) +- fi +- AC_SUBST(secure_statd) +-AC_ARG_ENABLE(rquotad, +- [AC_HELP_STRING([--enable-rquotad], +- [enable rquotad @<:@default=yes@:>@])], +- enable_rquotad=$enableval, +- enable_rquotad=yes) +- if test "$enable_rquotad" = yes; then +- RQUOTAD=rquotad +- else +- RQUOTAD= +- fi +- AM_CONDITIONAL(CONFIG_RQUOTAD, [test "$enable_rquotad" = "yes"]) +- +-AC_ARG_ENABLE(mount, +- [AC_HELP_STRING([--enable-mount], +- [Create mount.nfs and don't use the util-linux mount(8) functionality. @<:@default=no@:>@])], +- enable_mount=$enableval, +- enable_mount=no) +- AM_CONDITIONAL(CONFIG_MOUNT, [test "$enable_mount" = "yes"]) + + # Check whether user wants TCP wrappers support + AC_TCP_WRAPPERS +@@ -150,50 +71,18 @@ + AC_CHECK_LIB(socket, main, [LIBSOCKET="-lnsl"]) + AC_CHECK_LIB(nsl, main, [LIBNSL="-lnsl"]) + AC_CHECK_LIB(crypt, crypt, [LIBCRYPT="-lcrypt"]) +-if test "$enable_nfsv4" = yes; then +- AC_CHECK_LIB(event, event_dispatch, [libevent=1], AC_MSG_ERROR([libevent needed for nfsv4 support])) +- AC_CHECK_LIB(nfsidmap, nfs4_init_name_mapping, [libnfsidmap=1], AC_MSG_ERROR([libnfsidmap needed for nfsv4 support])) +- AC_CHECK_HEADERS(event.h, ,AC_MSG_ERROR([libevent needed for nfsv4 support])) +- AC_CHECK_HEADERS(nfsidmap.h, ,AC_MSG_ERROR([libnfsidmap needed for nfsv4 support])) +- dnl librpcsecgss already has a dependency on libgssapi, +- dnl but we need to make sure we get the right version + if test "$enable_gss" = yes; then +- PKG_CHECK_MODULES(RPCSECGSS, librpcsecgss >= 0.10, , +- [AC_MSG_ERROR([Unable to locate information required to use librpcsecgss.]) +- AC_MSG_ERROR([If you have pkgconfig installed, you might try setting environment]) +- AC_MSG_ERROR([variable PKG_CONFIG_PATH to /usr/local/lib/pkgconfig]) +- ] +- ) + PKG_CHECK_MODULES(GSSAPI, libgssapi >= 0.9) + fi + +-fi +-if test "$knfsd_cv_glibc2" = no; then +- AC_CHECK_LIB(bsd, daemon, [LIBBSD="-lbsd"]) +-fi + AC_SUBST(LIBSOCKET) + AC_SUBST(LIBNSL) + AC_SUBST(LIBCRYPT) + AC_SUBST(LIBBSD) + + if test "$enable_gss" = yes; then +- dnl 'gss' also depends on nfsidmap.h - at least for svcgssd_proc.c +- AC_CHECK_HEADERS(nfsidmap.h, ,AC_MSG_ERROR([libnfsidmap needed for gss support])) +- AC_CHECK_HEADERS(spkm3.h, ,AC_MSG_WARN([could not locate SPKM3 header; will not have SPKM3 support])) +- dnl the nfs4_set_debug function doesn't appear in all version of the library +- AC_CHECK_LIB(nfsidmap, nfs4_set_debug, +- AC_DEFINE(HAVE_NFS4_SET_DEBUG,1, +- [Whether nfs4_set_debug() is present in libnfsidmap]),) +- + dnl Check for Kerberos V5 + AC_KERBEROS_V5 +- +- dnl This is not done until here because we need to have KRBLIBS set +- dnl ("librpcsecgss=1" is so that it doesn't get added to LIBS) +- AC_CHECK_LIB(rpcsecgss, authgss_create_default, [librpcsecgss=1], AC_MSG_ERROR([librpcsecgss needed for nfsv4 support]), -lgssapi -ldl) +- AC_CHECK_LIB(rpcsecgss, authgss_set_debug_level, +- AC_DEFINE(HAVE_AUTHGSS_SET_DEBUG_LEVEL, 1, [Define this if the rpcsec_gss library has the function authgss_set_debug_level]),, -lgssapi -ldl) +- + fi + + dnl ************************************************************* +@@ -305,35 +194,7 @@ + + AC_CONFIG_FILES([ + Makefile +- linux-nfs/Makefile +- support/Makefile +- support/export/Makefile +- support/include/nfs/Makefile +- support/include/rpcsvc/Makefile +- support/include/sys/fs/Makefile +- support/include/sys/Makefile +- support/include/Makefile +- support/misc/Makefile +- support/nfs/Makefile +- tools/Makefile +- tools/getiversion/Makefile +- tools/getkversion/Makefile +- tools/locktest/Makefile +- tools/nlmtest/Makefile +- tools/rpcdebug/Makefile +- tools/rpcgen/Makefile + utils/Makefile +- utils/exportfs/Makefile +- utils/gssd/Makefile +- utils/idmapd/Makefile +- utils/lockd/Makefile +- utils/mount/Makefile +- utils/mountd/Makefile +- utils/nfsd/Makefile +- utils/nfsstat/Makefile +- utils/nhfsstone/Makefile +- utils/rquotad/Makefile +- utils/showmount/Makefile +- utils/statd/Makefile]) ++ utils/gssd/Makefile]) + AC_OUTPUT + +--- nfs-utils-1.0.10/Makefile.am.lustre 2006-08-07 00:40:50.000000000 -0600 ++++ nfs-utils-1.0.10/Makefile.am 2006-08-14 10:32:30.000000000 -0600 +@@ -1,6 +1,6 @@ + ## Process this file with automake to produce Makefile.in + +-SUBDIRS = tools support utils linux-nfs ++SUBDIRS = utils + + MAINTAINERCLEANFILES = Makefile.in + diff --git a/lustre/utils/gss/svcgssd.c b/lustre/utils/gss/svcgssd.c new file mode 100644 index 0000000..24cce81 --- /dev/null +++ b/lustre/utils/gss/svcgssd.c @@ -0,0 +1,254 @@ +/* + gssd.c + + Copyright (c) 2000 The Regents of the University of Michigan. + All rights reserved. + + Copyright (c) 2000 Dug Song . + Copyright (c) 2002 Andy Adamson . + Copyright (c) 2002 Marius Aamodt Eriksen . + Copyright (c) 2002 J. Bruce Fields . + All rights reserved, all wrongs reversed. + + 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. + +*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include "svcgssd.h" +#include "gss_util.h" +#include "err_util.h" +#include "lsupport.h" + +void +closeall(int min) +{ + DIR *dir = opendir("/proc/self/fd"); + if (dir != NULL) { + int dfd = dirfd(dir); + struct dirent *d; + + while ((d = readdir(dir)) != NULL) { + char *endp; + long n = strtol(d->d_name, &endp, 10); + if (*endp != '\0' && n >= min && n != dfd) + (void) close(n); + } + closedir(dir); + } else { + int fd = sysconf(_SC_OPEN_MAX); + while (--fd >= min) + (void) close(fd); + } +} +/* + * mydaemon creates a pipe between the partent and child + * process. The parent process will wait until the + * child dies or writes a '1' on the pipe signaling + * that it started successfully. + */ +int pipefds[2] = { -1, -1}; + +static void +mydaemon(int nochdir, int noclose) +{ + int pid, status, tempfd; + + if (pipe(pipefds) < 0) { + printerr(1, "mydaemon: pipe() failed: errno %d (%s)\n", + errno, strerror(errno)); + exit(1); + } + if ((pid = fork ()) < 0) { + printerr(1, "mydaemon: fork() failed: errno %d (%s)\n", + errno, strerror(errno)); + exit(1); + } + + if (pid != 0) { + /* + * Parent. Wait for status from child. + */ + close(pipefds[1]); + if (read(pipefds[0], &status, 1) != 1) + exit(1); + exit (0); + } + /* Child. */ + close(pipefds[0]); + setsid (); + if (nochdir == 0) { + if (chdir ("/") == -1) { + printerr(1, "mydaemon: chdir() failed: errno %d (%s)\n", + errno, strerror(errno)); + exit(1); + } + } + + while (pipefds[1] <= 2) { + pipefds[1] = dup(pipefds[1]); + if (pipefds[1] < 0) { + printerr(1, "mydaemon: dup() failed: errno %d (%s)\n", + errno, strerror(errno)); + exit(1); + } + } + + if (noclose == 0) { + tempfd = open("/dev/null", O_RDWR); + dup2(tempfd, 0); + dup2(tempfd, 1); + dup2(tempfd, 2); + closeall(3); + } + + return; +} + +static void +release_parent() +{ + int status; + + if (pipefds[1] > 0) { + write(pipefds[1], &status, 1); + close(pipefds[1]); + pipefds[1] = -1; + } +} + +void +sig_die(int signal) +{ + /* destroy krb5 machine creds */ + printerr(1, "exiting on signal %d\n", signal); + exit(1); +} + +void +sig_hup(int signal) +{ + /* don't exit on SIGHUP */ + printerr(1, "Received SIGHUP... Ignoring.\n"); + return; +} + +static void +usage(char *progname) +{ + fprintf(stderr, "usage: %s [-n] [-f] [-v] [-r]\n", + progname); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + int get_creds = 1; + int fg = 0; + int verbosity = 0; + int opt; + int must_srv_mds = 0, must_srv_oss = 0; + extern char *optarg; + char *progname; + + while ((opt = getopt(argc, argv, "fvrnp:")) != -1) { + switch (opt) { + case 'f': + fg = 1; + break; + case 'n': + get_creds = 0; + break; + case 'v': + verbosity++; + break; + case 'm': + get_creds = 1; + must_srv_mds = 1; + break; + case 'o': + get_creds = 1; + must_srv_oss = 1; + break; + default: + usage(argv[0]); + break; + } + } + + if ((progname = strrchr(argv[0], '/'))) + progname++; + else + progname = argv[0]; + + initerr(progname, verbosity, fg); + + if (gssd_check_mechs() != 0) { + printerr(0, "ERROR: Problem with gssapi library\n"); + exit(1); + } + + if (get_creds && gssd_prepare_creds(must_srv_mds, must_srv_oss)) { + printerr(0, "unable to obtain root (machine) credentials\n"); + printerr(0, "do you have a keytab entry for " + "nfs/@ in " + "/etc/krb5.keytab?\n"); + exit(1); + } + + if (!fg) + mydaemon(0, 0); + + signal(SIGINT, sig_die); + signal(SIGTERM, sig_die); + signal(SIGHUP, sig_hup); + + if (!fg) + release_parent(); + + gssd_init_unique(GSSD_SVC); + + svcgssd_run(); + printerr(0, "gssd_run returned!\n"); + abort(); +} diff --git a/lustre/utils/gss/svcgssd.h b/lustre/utils/gss/svcgssd.h new file mode 100644 index 0000000..dcf72a9 --- /dev/null +++ b/lustre/utils/gss/svcgssd.h @@ -0,0 +1,54 @@ +/* + 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 _RPC_SVCGSSD_H_ +#define _RPC_SVCGSSD_H_ + +#include +#include +#include + +void handle_nullreq(FILE *f); +void svcgssd_run(void); +int gssd_prepare_creds(int must_srv_mds, int must_srv_oss); +gss_cred_id_t gssd_select_svc_cred(int lustre_svc); + +extern char *mds_local_realm; +extern char *oss_local_realm; + +#define GSSD_SERVICE_NAME "lustre" + +/* XXX */ +#define GSSD_SERVICE_MDS "lustre_mds" +#define GSSD_SERVICE_OSS "lustre_oss" +#define LUSTRE_ROOT_NAME "lustre_root" +#define LUSTRE_ROOT_NAMELEN 11 + +#endif /* _RPC_SVCGSSD_H_ */ diff --git a/lustre/utils/gss/svcgssd_main_loop.c b/lustre/utils/gss/svcgssd_main_loop.c new file mode 100644 index 0000000..219ef65 --- /dev/null +++ b/lustre/utils/gss/svcgssd_main_loop.c @@ -0,0 +1,92 @@ +/* + 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. +*/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "svcgssd.h" +#include "err_util.h" + +void +svcgssd_run() +{ + int ret; + FILE *f; + struct pollfd pollfd; + +#define NULLRPC_FILE "/proc/net/rpc/auth.ptlrpcs.init/channel" + + while (1) { + int save_err; + + while ((f = fopen(NULLRPC_FILE, "rw")) == NULL) { + printerr(3, "failed to open %s: %s\n", + NULLRPC_FILE, strerror(errno)); + sleep(1); + } + pollfd.fd = fileno(f); + pollfd.events = POLLIN; + + pollfd.revents = 0; + printerr(3, "entering poll\n"); + ret = poll(&pollfd, 1, 1000); + save_err = errno; + printerr(3, "leaving poll\n"); + + if (ret < 0) { + if (save_err != EINTR) + printerr(0, "error return from poll: %s\n", + strerror(save_err)); + } else if (ret == 0) { + /* timeout; shouldn't happen. */ + } else { + if (ret != 1) { + printerr(0, "bug: unexpected poll return %d\n", + ret); + exit(1); + } + if (pollfd.revents & POLLIN) + handle_nullreq(f); + } + fclose(f); + } +} diff --git a/lustre/utils/gss/svcgssd_mech2file.c b/lustre/utils/gss/svcgssd_mech2file.c new file mode 100644 index 0000000..f44f7c6 --- /dev/null +++ b/lustre/utils/gss/svcgssd_mech2file.c @@ -0,0 +1,77 @@ +/* + linux_downcall.c + + Copyright (c) 2000 The Regents of the University of Michigan. + All rights reserved. + + Copyright (c) 2004 Andy Adamson . + All rights reserved, all wrongs reversed. + + 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. + +*/ + +#include "config.h" + +#ifdef HAVE_KRB5 +#include +#elif HAVE_HEIMDAL +#include +#endif +#include + + +#define g_OID_equal(o1,o2) \ + (((o1)->length == (o2)->length) && \ + (memcmp((o1)->elements,(o2)->elements,(int) (o1)->length) == 0)) + +struct mech2file { + gss_OID_desc mech; + char filename[8]; +}; + +struct mech2file m2f[] = { + {{9, "\052\206\110\206\367\022\001\002\002"}, "krb5"}, + {{7, "\053\006\001\005\005\001\003"}, "spkm3"}, + {{7, "\053\006\001\005\005\001\009"}, "lipkey"}, + {{0,0},""}, +}; + +/* + * Find the Linux svcgssd downcall file name given the mechanism + */ +char * +mech2file(gss_OID mech) +{ + struct mech2file *m2fp = m2f; + + while(m2fp->mech.length != 0) { + if (g_OID_equal(mech,&m2fp->mech)) + return(m2fp->filename); + m2fp++; + } + return NULL; +} diff --git a/lustre/utils/gss/svcgssd_proc.c b/lustre/utils/gss/svcgssd_proc.c new file mode 100644 index 0000000..e799f82 --- /dev/null +++ b/lustre/utils/gss/svcgssd_proc.c @@ -0,0 +1,537 @@ +/* + svc_in_gssd_proc.c + + Copyright (c) 2000 The Regents of the University of Michigan. + All rights reserved. + + Copyright (c) 2002 Bruce Fields + + 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. + +*/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "svcgssd.h" +#include "gss_util.h" +#include "err_util.h" +#include "context.h" +#include "cacheio.h" +#include "lsupport.h" + +extern char * mech2file(gss_OID mech); +#define SVCGSSD_CONTEXT_CHANNEL "/proc/net/rpc/auth.ptlrpcs.context/channel" +#define SVCGSSD_INIT_CHANNEL "/proc/net/rpc/auth.ptlrpcs.init/channel" + +#define TOKEN_BUF_SIZE 8192 + +struct svc_cred { + uint32_t cr_remote; + uint32_t cr_usr_root; + uint32_t cr_usr_mds; + uid_t cr_uid; + uid_t cr_mapped_uid; + uid_t cr_gid; +}; + +static int +do_svc_downcall(gss_buffer_desc *out_handle, struct svc_cred *cred, + gss_OID mech, gss_buffer_desc *context_token) +{ + FILE *f; + char *fname = NULL; + + printerr(2, "doing downcall\n"); + if ((fname = mech2file(mech)) == NULL) + goto out_err; + f = fopen(SVCGSSD_CONTEXT_CHANNEL, "w"); + if (f == NULL) { + printerr(0, "WARNING: unable to open downcall channel " + "%s: %s\n", + SVCGSSD_CONTEXT_CHANNEL, strerror(errno)); + goto out_err; + } + qword_printhex(f, out_handle->value, out_handle->length); + /* XXX are types OK for the rest of this? */ + qword_printint(f, 0x7fffffff); /*XXX need a better timeout */ + qword_printint(f, cred->cr_remote); + qword_printint(f, cred->cr_usr_root); + qword_printint(f, cred->cr_usr_mds); + qword_printint(f, cred->cr_mapped_uid); + qword_printint(f, cred->cr_uid); + qword_printint(f, cred->cr_gid); + qword_print(f, fname); + qword_printhex(f, context_token->value, context_token->length); + qword_eol(f); + fclose(f); + return 0; +out_err: + printerr(0, "WARNING: downcall failed\n"); + return -1; +} + +struct gss_verifier { + u_int32_t flav; + gss_buffer_desc body; +}; + +#define RPCSEC_GSS_SEQ_WIN 5 + +static int +send_response(FILE *f, gss_buffer_desc *in_handle, gss_buffer_desc *in_token, + u_int32_t maj_stat, u_int32_t min_stat, + gss_buffer_desc *out_handle, gss_buffer_desc *out_token) +{ + char buf[2 * TOKEN_BUF_SIZE]; + char *bp = buf; + int blen = sizeof(buf); + /* XXXARG: */ + int g; + + printerr(2, "sending null reply\n"); + + qword_addhex(&bp, &blen, in_handle->value, in_handle->length); + qword_addhex(&bp, &blen, in_token->value, in_token->length); + qword_addint(&bp, &blen, 0x7fffffff); /*XXX need a better timeout */ + qword_addint(&bp, &blen, maj_stat); + qword_addint(&bp, &blen, min_stat); + qword_addhex(&bp, &blen, out_handle->value, out_handle->length); + qword_addhex(&bp, &blen, out_token->value, out_token->length); + qword_addeol(&bp, &blen); + if (blen <= 0) { + printerr(0, "WARNING: send_respsonse: message too long\n"); + return -1; + } + g = open(SVCGSSD_INIT_CHANNEL, O_WRONLY); + if (g == -1) { + printerr(0, "WARNING: open %s failed: %s\n", + SVCGSSD_INIT_CHANNEL, strerror(errno)); + return -1; + } + *bp = '\0'; + printerr(3, "writing message: %s", buf); + if (write(g, buf, bp - buf) == -1) { + printerr(0, "WARNING: failed to write message\n"); + close(g); + return -1; + } + close(g); + return 0; +} + +#define rpc_auth_ok 0 +#define rpc_autherr_badcred 1 +#define rpc_autherr_rejectedcred 2 +#define rpc_autherr_badverf 3 +#define rpc_autherr_rejectedverf 4 +#define rpc_autherr_tooweak 5 +#define rpcsec_gsserr_credproblem 13 +#define rpcsec_gsserr_ctxproblem 14 + +#if 0 +static void +add_supplementary_groups(char *secname, char *name, struct svc_cred *cred) +{ + int ret; + static gid_t *groups = NULL; + + cred->cr_ngroups = NGROUPS; + ret = nfs4_gss_princ_to_grouplist(secname, name, + cred->cr_groups, &cred->cr_ngroups); + if (ret < 0) { + groups = realloc(groups, cred->cr_ngroups*sizeof(gid_t)); + ret = nfs4_gss_princ_to_grouplist(secname, name, + groups, &cred->cr_ngroups); + if (ret < 0) + cred->cr_ngroups = 0; + else { + if (cred->cr_ngroups > NGROUPS) + cred->cr_ngroups = NGROUPS; + memcpy(cred->cr_groups, groups, + cred->cr_ngroups*sizeof(gid_t)); + } + } +} +#endif + +#if 0 +static int +get_ids(gss_name_t client_name, gss_OID mech, struct svc_cred *cred) +{ + u_int32_t maj_stat, min_stat; + gss_buffer_desc name; + char *sname; + int res = -1; + uid_t uid, gid; + gss_OID name_type = GSS_C_NO_OID; + char *secname; + + maj_stat = gss_display_name(&min_stat, client_name, &name, &name_type); + if (maj_stat != GSS_S_COMPLETE) { + pgsserr("get_ids: gss_display_name", + maj_stat, min_stat, mech); + goto out; + } + if (name.length >= 0xffff || /* be certain name.length+1 doesn't overflow */ + !(sname = calloc(name.length + 1, 1))) { + printerr(0, "WARNING: get_ids: error allocating %d bytes " + "for sname\n", name.length + 1); + gss_release_buffer(&min_stat, &name); + goto out; + } + memcpy(sname, name.value, name.length); + printerr(1, "sname = %s\n", sname); + gss_release_buffer(&min_stat, &name); + + res = -EINVAL; + if ((secname = mech2file(mech)) == NULL) { + printerr(0, "WARNING: get_ids: error mapping mech to " + "file for name '%s'\n", sname); + goto out_free; + } + nfs4_init_name_mapping(NULL); /* XXX: should only do this once */ + res = nfs4_gss_princ_to_ids(secname, sname, &uid, &gid); + if (res < 0) { + /* + * -ENOENT means there was no mapping, any other error + * value means there was an error trying to do the + * mapping. + * If there was no mapping, we send down the value -1 + * to indicate that the anonuid/anongid for the export + * should be used. + */ + if (res == -ENOENT) { + cred->cr_uid = -1; + cred->cr_gid = -1; + cred->cr_ngroups = 0; + res = 0; + goto out_free; + } + printerr(0, "WARNING: get_ids: failed to map name '%s' " + "to uid/gid: %s\n", sname, strerror(-res)); + goto out_free; + } + cred->cr_uid = uid; + cred->cr_gid = gid; + add_supplementary_groups(secname, sname, cred); + res = 0; +out_free: + free(sname); +out: + return res; +} +#endif + +#if 0 +void +print_hexl(int pri, unsigned char *cp, int length) +{ + int i, j, jm; + unsigned char c; + + printerr(pri, "length %d\n",length); + printerr(pri, "\n"); + + for (i = 0; i < length; i += 0x10) { + printerr(pri, " %04x: ", (u_int)i); + jm = length - i; + jm = jm > 16 ? 16 : jm; + + for (j = 0; j < jm; j++) { + if ((j % 2) == 1) + printerr(pri,"%02x ", (u_int)cp[i+j]); + else + printerr(pri,"%02x", (u_int)cp[i+j]); + } + for (; j < 16; j++) { + if ((j % 2) == 1) + printerr(pri," "); + else + printerr(pri," "); + } + printerr(pri," "); + + for (j = 0; j < jm; j++) { + c = cp[i+j]; + c = isprint(c) ? c : '.'; + printerr(pri,"%c", c); + } + printerr(pri,"\n"); + } +} +#endif + +static int +get_ids(gss_name_t client_name, gss_OID mech, struct svc_cred *cred, + ptl_nid_t ptl_nid) +{ + u_int32_t maj_stat, min_stat; + gss_buffer_desc name; + char *sname, *realm, *slash; + int res = -1; + gss_OID name_type = GSS_C_NO_OID; + struct passwd *pw; + + memset(cred, 0, sizeof(*cred)); + + maj_stat = gss_display_name(&min_stat, client_name, &name, &name_type); + if (maj_stat != GSS_S_COMPLETE) { + pgsserr("get_ids: gss_display_name", + maj_stat, min_stat, mech); + return -1; + } + if (name.length >= 0xffff || /* be certain name.length+1 doesn't overflow */ + !(sname = calloc(name.length + 1, 1))) { + printerr(0, "WARNING: get_ids: error allocating %d bytes " + "for sname\n", name.length + 1); + gss_release_buffer(&min_stat, &name); + return -1; + } + memcpy(sname, name.value, name.length); + printerr(1, "authenticate user %s\n", sname); + gss_release_buffer(&min_stat, &name); + +#if 0 + lookup_mapping(sname, ptl_nal, ptl_netid, ptl_nid, &cred->cr_mapped_uid); +#else + cred->cr_mapped_uid = -1; +#endif + + realm = strchr(sname, '@'); + if (!realm) { + printerr(0, "WARNNING: principal %s contains no realm name\n", + sname); + cred->cr_remote = (mds_local_realm != NULL); + } else { + *realm++ = '\0'; + if (!mds_local_realm) + cred->cr_remote = 1; + else + cred->cr_remote = + (strcasecmp(mds_local_realm, realm) != 0); + } + + if (cred->cr_remote) { + if (cred->cr_mapped_uid != -1) + res = 0; + else + printerr(0, "principal %s is remote without mapping\n", + sname); + goto out_free; + } + + slash = strchr(sname, '/'); + if (slash) + *slash = '\0'; + + if (!(pw = getpwnam(sname))) { + /* If client use machine credential, we map it to root, which + * will subject to further mapping by root-squash in kernel. + * + * MDS service keytab is treated as special user, also mapped + * to root. OSS service keytab can't be used as a user. + */ + if (!strcmp(sname, LUSTRE_ROOT_NAME)) { + printerr(2, "lustre_root principal, resolve to uid 0\n"); + cred->cr_uid = 0; + cred->cr_usr_root = 1; + } else if (!strcmp(sname, GSSD_SERVICE_MDS)) { + printerr(2, "mds service principal, resolve to uid 0\n"); + cred->cr_uid = 0; + cred->cr_usr_mds = 1; + } else { + cred->cr_uid = -1; + if (cred->cr_mapped_uid == -1) { + printerr(0, "invalid user %s\n", sname); + goto out_free; + } + printerr(2, "user %s mapped to %u\n", + sname, cred->cr_mapped_uid); + } + } else { + cred->cr_uid = pw->pw_uid; + printerr(2, "%s resolve to uid %u\n", sname, cred->cr_uid); + } + + res = 0; +out_free: + free(sname); + return res; +} + +typedef struct gss_union_ctx_id_t { + gss_OID mech_type; + gss_ctx_id_t internal_ctx_id; +} gss_union_ctx_id_desc, *gss_union_ctx_id_t; + +void +handle_nullreq(FILE *f) { + uint64_t handle_seq; + char in_tok_buf[TOKEN_BUF_SIZE]; + char in_handle_buf[15]; + char out_handle_buf[15]; + gss_buffer_desc in_tok = {.value = in_tok_buf}, + out_tok = {.value = NULL}, + in_handle = {.value = in_handle_buf}, + out_handle = {.value = out_handle_buf}, + ctx_token = {.value = NULL}, + ignore_out_tok = {.value = NULL}, + /* XXX isn't there a define for this?: */ + null_token = {.value = NULL}; + uint32_t lustre_svc; + uint64_t ptl_nid; + u_int32_t ret_flags; + gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; + gss_name_t client_name; + gss_OID mech = GSS_C_NO_OID; + gss_cred_id_t svc_cred; + u_int32_t maj_stat = GSS_S_FAILURE, min_stat = 0; + u_int32_t ignore_min_stat; + struct svc_cred cred; + static char *lbuf = NULL; + static int lbuflen = 0; + static char *cp; + + printerr(2, "handling null request\n"); + + if (readline(fileno(f), &lbuf, &lbuflen) != 1) { + printerr(0, "WARNING: handle_nullreq: " + "failed reading request\n"); + return; + } + + cp = lbuf; + + qword_get(&cp, (char *) &lustre_svc, sizeof(lustre_svc)); + qword_get(&cp, (char *) &ptl_nid, sizeof(ptl_nid)); + qword_get(&cp, (char *) &handle_seq, sizeof(handle_seq)); + printerr(1, "handling req: svc %u, nid %016llx, idx %llx\n", + lustre_svc, ptl_nid, handle_seq); + + in_handle.length = (size_t) qword_get(&cp, in_handle.value, + sizeof(in_handle_buf)); + printerr(3, "in_handle: \n"); + print_hexl(3, in_handle.value, in_handle.length); + + in_tok.length = (size_t) qword_get(&cp, in_tok.value, + sizeof(in_tok_buf)); + printerr(3, "in_tok: \n"); + print_hexl(3, in_tok.value, in_tok.length); + + if (in_tok.length < 0) { + printerr(0, "WARNING: handle_nullreq: " + "failed parsing request\n"); + goto out_err; + } + + if (in_handle.length != 0) { /* CONTINUE_INIT case */ + if (in_handle.length != sizeof(ctx)) { + printerr(0, "WARNING: handle_nullreq: " + "input handle has unexpected length %d\n", + in_handle.length); + goto out_err; + } + /* in_handle is the context id stored in the out_handle + * for the GSS_S_CONTINUE_NEEDED case below. */ + memcpy(&ctx, in_handle.value, in_handle.length); + } + + svc_cred = gssd_select_svc_cred(lustre_svc); + if (!svc_cred) { + printerr(0, "no service credential for svc %u\n", lustre_svc); + goto out_err; + } + + maj_stat = gss_accept_sec_context(&min_stat, &ctx, svc_cred, + &in_tok, GSS_C_NO_CHANNEL_BINDINGS, &client_name, + &mech, &out_tok, &ret_flags, NULL, NULL); + + if (maj_stat == GSS_S_CONTINUE_NEEDED) { + printerr(1, "gss_accept_sec_context GSS_S_CONTINUE_NEEDED\n"); + + /* Save the context handle for future calls */ + out_handle.length = sizeof(ctx); + memcpy(out_handle.value, &ctx, sizeof(ctx)); + goto continue_needed; + } + else if (maj_stat != GSS_S_COMPLETE) { + printerr(0, "WARNING: gss_accept_sec_context failed\n"); + pgsserr("handle_nullreq: gss_accept_sec_context", + maj_stat, min_stat, mech); + goto out_err; + } + if (get_ids(client_name, mech, &cred, ptl_nid)) { + /* get_ids() prints error msg */ + maj_stat = GSS_S_BAD_NAME; /* XXX ? */ + gss_release_name(&ignore_min_stat, &client_name); + goto out_err; + } + gss_release_name(&ignore_min_stat, &client_name); + + /* Context complete. Pass handle_seq in out_handle to use + * for context lookup in the kernel. */ + out_handle.length = sizeof(handle_seq); + memcpy(out_handle.value, &handle_seq, sizeof(handle_seq)); + + /* kernel needs ctx to calculate verifier on null response, so + * must give it context before doing null call: */ + if (serialize_context_for_kernel(ctx, &ctx_token, mech)) { + printerr(0, "WARNING: handle_nullreq: " + "serialize_context_for_kernel failed\n"); + maj_stat = GSS_S_FAILURE; + goto out_err; + } + /* We no longer need the gss context */ + gss_delete_sec_context(&ignore_min_stat, &ctx, &ignore_out_tok); + + do_svc_downcall(&out_handle, &cred, mech, &ctx_token); +continue_needed: + send_response(f, &in_handle, &in_tok, maj_stat, min_stat, + &out_handle, &out_tok); +out: + if (ctx_token.value != NULL) + free(ctx_token.value); + if (out_tok.value != NULL) + gss_release_buffer(&ignore_min_stat, &out_tok); + return; + +out_err: + if (ctx != GSS_C_NO_CONTEXT) + gss_delete_sec_context(&ignore_min_stat, &ctx, &ignore_out_tok); + send_response(f, &in_handle, &in_tok, maj_stat, min_stat, + &null_token, &null_token); + goto out; +} diff --git a/lustre/utils/gss/write_bytes.h b/lustre/utils/gss/write_bytes.h new file mode 100644 index 0000000..4fc72cc --- /dev/null +++ b/lustre/utils/gss/write_bytes.h @@ -0,0 +1,158 @@ +/* + 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 _WRITE_BYTES_H_ +#define _WRITE_BYTES_H_ + +#include +#include +#include /* for ntohl */ + +inline static int +write_bytes(char **ptr, const char *end, const void *arg, int arg_len) +{ + char *p = *ptr, *arg_end; + + arg_end = p + arg_len; + if (arg_end > end || arg_end < p) + return -1; + memcpy(p, arg, arg_len); + *ptr = arg_end; + return 0; +} + +#define WRITE_BYTES(p, end, arg) write_bytes(p, end, &arg, sizeof(arg)) + +inline static int +write_buffer(char **p, char *end, gss_buffer_desc *arg) +{ + int len = (int)arg->length; /* make an int out of size_t */ + if (WRITE_BYTES(p, end, len)) + return -1; + if (*p + len > end) + return -1; + memcpy(*p, arg->value, len); + *p += len; + return 0; +} + +inline static int +write_oid(char **p, char *end, gss_OID_desc *arg) +{ + int len = (int)arg->length; /* make an int out of size_t */ + if (WRITE_BYTES(p, end, len)) + return -1; + if (*p + arg->length > end) + return -1; + memcpy(*p, arg->elements, len); + *p += len; + return 0; +} + +static inline int +get_bytes(char **ptr, const char *end, void *res, int len) +{ + char *p, *q; + p = *ptr; + q = p + len; + if (q > end || q < p) + return -1; + memcpy(res, p, len); + *ptr = q; + return 0; +} + +static inline int +get_buffer(char **ptr, const char *end, gss_buffer_desc *res) +{ + char *p, *q; + p = *ptr; + int len; + if (get_bytes(&p, end, &len, sizeof(len))) + return -1; + res->length = len; /* promote to size_t if necessary */ + q = p + res->length; + if (q > end || q < p) + return -1; + if (!(res->value = malloc(res->length))) + return -1; + memcpy(res->value, p, res->length); + *ptr = q; + return 0; +} + +static inline int +xdr_get_u32(u_int32_t **ptr, const u_int32_t *end, u_int32_t *res) +{ + if (get_bytes((char **)ptr, (char *)end, res, sizeof(res))) + return -1; + *res = ntohl(*res); + return 0; +} + +static inline int +xdr_get_buffer(u_int32_t **ptr, const u_int32_t *end, gss_buffer_desc *res) +{ + u_int32_t *p, *q; + u_int32_t len; + p = *ptr; + if (xdr_get_u32(&p, end, &len)) + return -1; + res->length = len; + q = p + ((res->length + 3) >> 2); + if (q > end || q < p) + return -1; + if (!(res->value = malloc(res->length))) + return -1; + memcpy(res->value, p, res->length); + *ptr = q; + return 0; +} + +static inline int +xdr_write_u32(u_int32_t **ptr, const u_int32_t *end, u_int32_t arg) +{ + u_int32_t tmp; + + tmp = htonl(arg); + return WRITE_BYTES((char **)ptr, (char *)end, tmp); +} + +static inline int +xdr_write_buffer(u_int32_t **ptr, const u_int32_t *end, gss_buffer_desc *arg) +{ + int len = arg->length; + if (xdr_write_u32(ptr, end, len)) + return -1; + return write_bytes((char **)ptr, (char *)end, arg->value, + (arg->length + 3) & ~3); +} + +#endif /* _WRITE_BYTES_H_ */ diff --git a/lustre/utils/lconf b/lustre/utils/lconf index 9cce534..423e536 100755 --- a/lustre/utils/lconf +++ b/lustre/utils/lconf @@ -1143,6 +1143,7 @@ class LDLM(Module): self.add_lustre_module('lvfs', 'lvfs') self.add_lustre_module('obdclass', 'obdclass') self.add_lustre_module('ptlrpc', 'ptlrpc') + self.add_lustre_module('ptlrpc/gss', 'ptlrpc_gss') def prepare(self): return diff --git a/lustre/utils/module_setup.sh b/lustre/utils/module_setup.sh index 8d11978..b2a4353 100755 --- a/lustre/utils/module_setup.sh +++ b/lustre/utils/module_setup.sh @@ -27,6 +27,7 @@ cp -u ../../lustre/quota/lquota.$EXT $MDIR cp -u ../../lustre/quota/quotacheck_test.$EXT $MDIR cp -u ../../lustre/quota/quotactl_test.$EXT $MDIR cp -u ../../lustre/ptlrpc/ptlrpc.$EXT $MDIR +cp -u ../../lustre/ptlrpc/gss/ptlrpc_gss.$EXT $MDIR cp -u ../../lustre/fld/fld.$EXT $MDIR cp -u ../../lustre/lov/lov.$EXT $MDIR cp -u ../../lustre/mdc/mdc.$EXT $MDIR