Commit b7856020 authored by Greg Kroah-Hartman's avatar Greg Kroah-Hartman

staging: lustre: ptlrpc: gss: delete unused code

The gss code has never been built, there is no Kconfig option for it, so
delete it as code that can not build goes bad really fast.

If someone wants it back, they can revert this and fix any build errors
that might be in it.

Cc: Andreas Dilger <andreas.dilger@intel.com>
Cc: Oleg Drokin <oleg.drokin@intel.com>
Cc: hpdd-discuss <hpdd-discuss@lists.01.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent e27db149
......@@ -18,5 +18,3 @@ ptlrpc_objs += sec_null.o sec_plain.o nrs.o nrs_fifo.o
ptlrpc-y := $(ldlm_objs) $(ptlrpc_objs)
ptlrpc-$(CONFIG_PROC_FS) += sec_lproc.o
ptlrpc-$(CONFIG_LUSTRE_TRANSLATE_ERRNOS) += errno.o
obj-$(CONFIG_PTLRPC_GSS) += gss/
obj-$(CONFIG_LUSTRE_FS) := ptlrpc_gss.o
ptlrpc_gss-y := 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
ccflags-y := -I$(src)/../include
/*
* Modifications for Lustre
*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Author: Eric Mei <ericm@clusterfs.com>
*/
/*
* Somewhat simplified version of the gss api.
*
* Dug Song <dugsong@monkey.org>
* Andy Adamson <andros@umich.edu>
* Bruce Fields <bfields@umich.edu>
* 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);
__u32 lgss_copy_reverse_context(
struct gss_ctx *ctx,
struct gss_ctx **ctx_new);
__u32 lgss_inquire_context(
struct gss_ctx *ctx,
unsigned long *endtime);
__u32 lgss_get_mic(
struct gss_ctx *ctx,
int msgcnt,
rawobj_t *msgs,
int iovcnt,
lnet_kiov_t *iovs,
rawobj_t *mic_token);
__u32 lgss_verify_mic(
struct gss_ctx *ctx,
int msgcnt,
rawobj_t *msgs,
int iovcnt,
lnet_kiov_t *iovs,
rawobj_t *mic_token);
__u32 lgss_wrap(
struct gss_ctx *ctx,
rawobj_t *gsshdr,
rawobj_t *msg,
int msg_buflen,
rawobj_t *out_token);
__u32 lgss_unwrap(
struct gss_ctx *ctx,
rawobj_t *gsshdr,
rawobj_t *token,
rawobj_t *out_msg);
__u32 lgss_prep_bulk(
struct gss_ctx *gctx,
struct ptlrpc_bulk_desc *desc);
__u32 lgss_wrap_bulk(
struct gss_ctx *gctx,
struct ptlrpc_bulk_desc *desc,
rawobj_t *token,
int adj_nob);
__u32 lgss_unwrap_bulk(
struct gss_ctx *gctx,
struct ptlrpc_bulk_desc *desc,
rawobj_t *token,
int adj_nob);
__u32 lgss_delete_sec_context(
struct gss_ctx **ctx);
int lgss_display(
struct gss_ctx *ctx,
char *buf,
int bufsize);
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);
__u32 (*gss_copy_reverse_context)(
struct gss_ctx *ctx,
struct gss_ctx *ctx_new);
__u32 (*gss_inquire_context)(
struct gss_ctx *ctx,
unsigned long *endtime);
__u32 (*gss_get_mic)(
struct gss_ctx *ctx,
int msgcnt,
rawobj_t *msgs,
int iovcnt,
lnet_kiov_t *iovs,
rawobj_t *mic_token);
__u32 (*gss_verify_mic)(
struct gss_ctx *ctx,
int msgcnt,
rawobj_t *msgs,
int iovcnt,
lnet_kiov_t *iovs,
rawobj_t *mic_token);
__u32 (*gss_wrap)(
struct gss_ctx *ctx,
rawobj_t *gsshdr,
rawobj_t *msg,
int msg_buflen,
rawobj_t *out_token);
__u32 (*gss_unwrap)(
struct gss_ctx *ctx,
rawobj_t *gsshdr,
rawobj_t *token,
rawobj_t *out_msg);
__u32 (*gss_prep_bulk)(
struct gss_ctx *gctx,
struct ptlrpc_bulk_desc *desc);
__u32 (*gss_wrap_bulk)(
struct gss_ctx *gctx,
struct ptlrpc_bulk_desc *desc,
rawobj_t *token,
int adj_nob);
__u32 (*gss_unwrap_bulk)(
struct gss_ctx *gctx,
struct ptlrpc_bulk_desc *desc,
rawobj_t *token,
int adj_nob);
void (*gss_delete_sec_context)(
void *ctx);
int (*gss_display)(
struct gss_ctx *ctx,
char *buf,
int bufsize);
};
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_ */
/*
* Modifications for Lustre
*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Author: Eric Mei <ericm@clusterfs.com>
*/
/*
* 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 <andros@umich.edu>
*/
/*
* 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);
/*
* GPL HEADER START
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 only,
* as published by the Free Software Foundation.
*
* This program 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 version 2 for more details (a copy is included
* in the LICENSE file that accompanied this code).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; If not, see
* http://www.sun.com/software/products/lustre/docs/GPLv2.pdf
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*
* GPL HEADER END
*/
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
* Use is subject to license terms.
*/
/*
* This file is part of Lustre, http://www.lustre.org/
* Lustre is a trademark of Sun Microsystems, Inc.
*
* lustre/ptlrpc/gss/gss_bulk.c
*
* Author: Eric Mei <eric.mei@sun.com>
*/
#define DEBUG_SUBSYSTEM S_SEC
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/dcache.h>
#include <linux/fs.h>
#include <linux/mutex.h>
#include <linux/crypto.h>
#include <obd.h>
#include <obd_class.h>
#include <obd_support.h>
#include <lustre/lustre_idl.h>
#include <lustre_net.h>
#include <lustre_import.h>
#include <lustre_sec.h>
#include "gss_err.h"
#include "gss_internal.h"
#include "gss_api.h"
int gss_cli_ctx_wrap_bulk(struct ptlrpc_cli_ctx *ctx,
struct ptlrpc_request *req,
struct ptlrpc_bulk_desc *desc)
{
struct gss_cli_ctx *gctx;
struct lustre_msg *msg;
struct ptlrpc_bulk_sec_desc *bsd;
rawobj_t token;
__u32 maj;
int offset;
int rc;
LASSERT(req->rq_pack_bulk);
LASSERT(req->rq_bulk_read || req->rq_bulk_write);
gctx = container_of(ctx, struct gss_cli_ctx, gc_base);
LASSERT(gctx->gc_mechctx);
switch (SPTLRPC_FLVR_SVC(req->rq_flvr.sf_rpc)) {
case SPTLRPC_SVC_NULL:
LASSERT(req->rq_reqbuf->lm_bufcount >= 3);
msg = req->rq_reqbuf;
offset = msg->lm_bufcount - 1;
break;
case SPTLRPC_SVC_AUTH:
case SPTLRPC_SVC_INTG:
LASSERT(req->rq_reqbuf->lm_bufcount >= 4);
msg = req->rq_reqbuf;
offset = msg->lm_bufcount - 2;
break;
case SPTLRPC_SVC_PRIV:
LASSERT(req->rq_clrbuf->lm_bufcount >= 2);
msg = req->rq_clrbuf;
offset = msg->lm_bufcount - 1;
break;
default:
LBUG();
}
bsd = lustre_msg_buf(msg, offset, sizeof(*bsd));
bsd->bsd_version = 0;
bsd->bsd_flags = 0;
bsd->bsd_type = SPTLRPC_BULK_DEFAULT;
bsd->bsd_svc = SPTLRPC_FLVR_BULK_SVC(req->rq_flvr.sf_rpc);
if (bsd->bsd_svc == SPTLRPC_BULK_SVC_NULL)
return 0;
LASSERT(bsd->bsd_svc == SPTLRPC_BULK_SVC_INTG ||
bsd->bsd_svc == SPTLRPC_BULK_SVC_PRIV);
if (req->rq_bulk_read) {
/*
* bulk read: prepare receiving pages only for privacy mode.
*/
if (bsd->bsd_svc == SPTLRPC_BULK_SVC_PRIV)
return gss_cli_prep_bulk(req, desc);
} else {
/*
* bulk write: sign or encrypt bulk pages.
*/
bsd->bsd_nob = desc->bd_nob;
if (bsd->bsd_svc == SPTLRPC_BULK_SVC_INTG) {
/* integrity mode */
token.data = bsd->bsd_data;
token.len = lustre_msg_buflen(msg, offset) -
sizeof(*bsd);
maj = lgss_get_mic(gctx->gc_mechctx, 0, NULL,
desc->bd_iov_count, desc->bd_iov,
&token);
if (maj != GSS_S_COMPLETE) {
CWARN("failed to sign bulk data: %x\n", maj);
return -EACCES;
}
} else {
/* privacy mode */
if (desc->bd_iov_count == 0)
return 0;
rc = sptlrpc_enc_pool_get_pages(desc);
if (rc) {
CERROR("bulk write: failed to allocate "
"encryption pages: %d\n", rc);
return rc;
}
token.data = bsd->bsd_data;
token.len = lustre_msg_buflen(msg, offset) -
sizeof(*bsd);
maj = lgss_wrap_bulk(gctx->gc_mechctx, desc, &token, 0);
if (maj != GSS_S_COMPLETE) {
CWARN("fail to encrypt bulk data: %x\n", maj);
return -EACCES;
}
}
}
return 0;
}
int gss_cli_ctx_unwrap_bulk(struct ptlrpc_cli_ctx *ctx,
struct ptlrpc_request *req,
struct ptlrpc_bulk_desc *desc)
{
struct gss_cli_ctx *gctx;
struct lustre_msg *rmsg, *vmsg;
struct ptlrpc_bulk_sec_desc *bsdr, *bsdv;
rawobj_t token;
__u32 maj;
int roff, voff;
LASSERT(req->rq_pack_bulk);
LASSERT(req->rq_bulk_read || req->rq_bulk_write);
switch (SPTLRPC_FLVR_SVC(req->rq_flvr.sf_rpc)) {
case SPTLRPC_SVC_NULL:
vmsg = req->rq_repdata;
LASSERT(vmsg != NULL && vmsg->lm_bufcount >= 3);
voff = vmsg->lm_bufcount - 1;
rmsg = req->rq_reqbuf;
LASSERT(rmsg != NULL && rmsg->lm_bufcount >= 3);
roff = rmsg->lm_bufcount - 1; /* last segment */
break;
case SPTLRPC_SVC_AUTH:
case SPTLRPC_SVC_INTG:
vmsg = req->rq_repdata;
LASSERT(vmsg != NULL && vmsg->lm_bufcount >= 4);
voff = vmsg->lm_bufcount - 2;
rmsg = req->rq_reqbuf;
LASSERT(rmsg != NULL && rmsg->lm_bufcount >= 4);
roff = rmsg->lm_bufcount - 2; /* second last segment */
break;
case SPTLRPC_SVC_PRIV:
vmsg = req->rq_repdata;
LASSERT(vmsg != NULL && vmsg->lm_bufcount >= 2);
voff = vmsg->lm_bufcount - 1;
rmsg = req->rq_clrbuf;
LASSERT(rmsg != NULL && rmsg->lm_bufcount >= 2);
roff = rmsg->lm_bufcount - 1; /* last segment */
break;
default:
LBUG();
}
bsdr = lustre_msg_buf(rmsg, roff, sizeof(*bsdr));
bsdv = lustre_msg_buf(vmsg, voff, sizeof(*bsdv));
LASSERT(bsdr && bsdv);
if (bsdr->bsd_version != bsdv->bsd_version ||
bsdr->bsd_type != bsdv->bsd_type ||
bsdr->bsd_svc != bsdv->bsd_svc) {
CERROR("bulk security descriptor mismatch: "
"(%u,%u,%u) != (%u,%u,%u)\n",
bsdr->bsd_version, bsdr->bsd_type, bsdr->bsd_svc,
bsdv->bsd_version, bsdv->bsd_type, bsdv->bsd_svc);
return -EPROTO;
}
LASSERT(bsdv->bsd_svc == SPTLRPC_BULK_SVC_NULL ||
bsdv->bsd_svc == SPTLRPC_BULK_SVC_INTG ||
bsdv->bsd_svc == SPTLRPC_BULK_SVC_PRIV);
/*
* in privacy mode if return success, make sure bd_nob_transferred
* is the actual size of the clear text, otherwise upper layer
* may be surprised.
*/
if (req->rq_bulk_write) {
if (bsdv->bsd_flags & BSD_FL_ERR) {
CERROR("server reported bulk i/o failure\n");
return -EIO;
}
if (bsdv->bsd_svc == SPTLRPC_BULK_SVC_PRIV)
desc->bd_nob_transferred = desc->bd_nob;
} else {
/*
* bulk read, upon return success, bd_nob_transferred is
* the size of plain text actually received.
*/
gctx = container_of(ctx, struct gss_cli_ctx, gc_base);
LASSERT(gctx->gc_mechctx);
if (bsdv->bsd_svc == SPTLRPC_BULK_SVC_INTG) {
int i, nob;
/* fix the actual data size */
for (i = 0, nob = 0; i < desc->bd_iov_count; i++) {
if (desc->bd_iov[i].kiov_len + nob >
desc->bd_nob_transferred) {
desc->bd_iov[i].kiov_len =
desc->bd_nob_transferred - nob;
}
nob += desc->bd_iov[i].kiov_len;
}
token.data = bsdv->bsd_data;
token.len = lustre_msg_buflen(vmsg, voff) -
sizeof(*bsdv);
maj = lgss_verify_mic(gctx->gc_mechctx, 0, NULL,
desc->bd_iov_count, desc->bd_iov,
&token);
if (maj != GSS_S_COMPLETE) {
CERROR("failed to verify bulk read: %x\n", maj);
return -EACCES;
}
} else if (bsdv->bsd_svc == SPTLRPC_BULK_SVC_PRIV) {
desc->bd_nob = bsdv->bsd_nob;
if (desc->bd_nob == 0)
return 0;
token.data = bsdv->bsd_data;
token.len = lustre_msg_buflen(vmsg, voff) -
sizeof(*bsdr);
maj = lgss_unwrap_bulk(gctx->gc_mechctx, desc,
&token, 1);
if (maj != GSS_S_COMPLETE) {
CERROR("failed to decrypt bulk read: %x\n",
maj);
return -EACCES;
}
desc->bd_nob_transferred = desc->bd_nob;
}
}
return 0;
}
static int gss_prep_bulk(struct ptlrpc_bulk_desc *desc,
struct gss_ctx *mechctx)
{
int rc;
if (desc->bd_iov_count == 0)
return 0;
rc = sptlrpc_enc_pool_get_pages(desc);
if (rc)
return rc;
if (lgss_prep_bulk(mechctx, desc) != GSS_S_COMPLETE)
return -EACCES;
return 0;
}
int gss_cli_prep_bulk(struct ptlrpc_request *req,
struct ptlrpc_bulk_desc *desc)
{
int rc;
LASSERT(req->rq_cli_ctx);
LASSERT(req->rq_pack_bulk);
LASSERT(req->rq_bulk_read);
if (SPTLRPC_FLVR_BULK_SVC(req->rq_flvr.sf_rpc) != SPTLRPC_BULK_SVC_PRIV)
return 0;
rc = gss_prep_bulk(desc, ctx2gctx(req->rq_cli_ctx)->gc_mechctx);
if (rc)
CERROR("bulk read: failed to prepare encryption "
"pages: %d\n", rc);
return rc;
}
int gss_svc_prep_bulk(struct ptlrpc_request *req,
struct ptlrpc_bulk_desc *desc)
{
struct gss_svc_reqctx *grctx;
struct ptlrpc_bulk_sec_desc *bsd;
int rc;
LASSERT(req->rq_svc_ctx);
LASSERT(req->rq_pack_bulk);
LASSERT(req->rq_bulk_write);
grctx = gss_svc_ctx2reqctx(req->rq_svc_ctx);
LASSERT(grctx->src_reqbsd);
LASSERT(grctx->src_repbsd);
LASSERT(grctx->src_ctx);
LASSERT(grctx->src_ctx->gsc_mechctx);
bsd = grctx->src_reqbsd;
if (bsd->bsd_svc != SPTLRPC_BULK_SVC_PRIV)
return 0;
rc = gss_prep_bulk(desc, grctx->src_ctx->gsc_mechctx);
if (rc)
CERROR("bulk write: failed to prepare encryption "
"pages: %d\n", rc);
return rc;
}
int gss_svc_unwrap_bulk(struct ptlrpc_request *req,
struct ptlrpc_bulk_desc *desc)
{
struct gss_svc_reqctx *grctx;
struct ptlrpc_bulk_sec_desc *bsdr, *bsdv;
rawobj_t token;
__u32 maj;
LASSERT(req->rq_svc_ctx);
LASSERT(req->rq_pack_bulk);
LASSERT(req->rq_bulk_write);
grctx = gss_svc_ctx2reqctx(req->rq_svc_ctx);
LASSERT(grctx->src_reqbsd);
LASSERT(grctx->src_repbsd);
LASSERT(grctx->src_ctx);
LASSERT(grctx->src_ctx->gsc_mechctx);
bsdr = grctx->src_reqbsd;
bsdv = grctx->src_repbsd;
/* bsdr has been sanity checked during unpacking */
bsdv->bsd_version = 0;
bsdv->bsd_type = SPTLRPC_BULK_DEFAULT;
bsdv->bsd_svc = bsdr->bsd_svc;
bsdv->bsd_flags = 0;
switch (bsdv->bsd_svc) {
case SPTLRPC_BULK_SVC_INTG:
token.data = bsdr->bsd_data;
token.len = grctx->src_reqbsd_size - sizeof(*bsdr);
maj = lgss_verify_mic(grctx->src_ctx->gsc_mechctx, 0, NULL,
desc->bd_iov_count, desc->bd_iov, &token);
if (maj != GSS_S_COMPLETE) {
bsdv->bsd_flags |= BSD_FL_ERR;
CERROR("failed to verify bulk signature: %x\n", maj);
return -EACCES;
}
break;
case SPTLRPC_BULK_SVC_PRIV:
if (bsdr->bsd_nob != desc->bd_nob) {
bsdv->bsd_flags |= BSD_FL_ERR;
CERROR("prepared nob %d doesn't match the actual "
"nob %d\n", desc->bd_nob, bsdr->bsd_nob);
return -EPROTO;
}
if (desc->bd_iov_count == 0) {
LASSERT(desc->bd_nob == 0);
break;
}
token.data = bsdr->bsd_data;
token.len = grctx->src_reqbsd_size - sizeof(*bsdr);
maj = lgss_unwrap_bulk(grctx->src_ctx->gsc_mechctx,
desc, &token, 0);
if (maj != GSS_S_COMPLETE) {
bsdv->bsd_flags |= BSD_FL_ERR;
CERROR("failed decrypt bulk data: %x\n", maj);
return -EACCES;
}
break;
}
return 0;
}
int gss_svc_wrap_bulk(struct ptlrpc_request *req,
struct ptlrpc_bulk_desc *desc)
{
struct gss_svc_reqctx *grctx;
struct ptlrpc_bulk_sec_desc *bsdr, *bsdv;
rawobj_t token;
__u32 maj;
int rc;
LASSERT(req->rq_svc_ctx);
LASSERT(req->rq_pack_bulk);
LASSERT(req->rq_bulk_read);
grctx = gss_svc_ctx2reqctx(req->rq_svc_ctx);
LASSERT(grctx->src_reqbsd);
LASSERT(grctx->src_repbsd);
LASSERT(grctx->src_ctx);
LASSERT(grctx->src_ctx->gsc_mechctx);
bsdr = grctx->src_reqbsd;
bsdv = grctx->src_repbsd;
/* bsdr has been sanity checked during unpacking */
bsdv->bsd_version = 0;
bsdv->bsd_type = SPTLRPC_BULK_DEFAULT;
bsdv->bsd_svc = bsdr->bsd_svc;
bsdv->bsd_flags = 0;
switch (bsdv->bsd_svc) {
case SPTLRPC_BULK_SVC_INTG:
token.data = bsdv->bsd_data;
token.len = grctx->src_repbsd_size - sizeof(*bsdv);
maj = lgss_get_mic(grctx->src_ctx->gsc_mechctx, 0, NULL,
desc->bd_iov_count, desc->bd_iov, &token);
if (maj != GSS_S_COMPLETE) {
bsdv->bsd_flags |= BSD_FL_ERR;
CERROR("failed to sign bulk data: %x\n", maj);
return -EACCES;
}
break;
case SPTLRPC_BULK_SVC_PRIV:
bsdv->bsd_nob = desc->bd_nob;
if (desc->bd_iov_count == 0) {
LASSERT(desc->bd_nob == 0);
break;
}
rc = sptlrpc_enc_pool_get_pages(desc);
if (rc) {
bsdv->bsd_flags |= BSD_FL_ERR;
CERROR("bulk read: failed to allocate encryption "
"pages: %d\n", rc);
return rc;
}
token.data = bsdv->bsd_data;
token.len = grctx->src_repbsd_size - sizeof(*bsdv);
maj = lgss_wrap_bulk(grctx->src_ctx->gsc_mechctx,
desc, &token, 1);
if (maj != GSS_S_COMPLETE) {
bsdv->bsd_flags |= BSD_FL_ERR;
CERROR("failed to encrypt bulk data: %x\n", maj);
return -EACCES;
}
break;
}
return 0;
}
/*
* GPL HEADER START
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 only,
* as published by the Free Software Foundation.
*
* This program 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 version 2 for more details (a copy is included
* in the LICENSE file that accompanied this code).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; If not, see
* http://www.sun.com/software/products/lustre/docs/GPLv2.pdf
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*
* GPL HEADER END
*/
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
* Use is subject to license terms.
*
* Copyright (c) 2011, 2012, Intel Corporation.
*/
/*
* This file is part of Lustre, http://www.lustre.org/
* Lustre is a trademark of Sun Microsystems, Inc.
*
* lustre/ptlrpc/gss/gss_cli_upcall.c
*
* Author: Eric Mei <ericm@clusterfs.com>
*/
#define DEBUG_SUBSYSTEM S_SEC
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/dcache.h>
#include <linux/fs.h>
#include <linux/mutex.h>
#include <obd.h>
#include <obd_class.h>
#include <obd_support.h>
#include <lustre/lustre_idl.h>
#include <lustre_net.h>
#include <lustre_import.h>
#include <lustre_sec.h>
#include "gss_err.h"
#include "gss_internal.h"
#include "gss_api.h"
/**********************************************
* 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);
LASSERT(req->rq_cli_ctx);
LASSERT(req->rq_cli_ctx->cc_sec);
/* gss hdr */
ghdr = lustre_msg_buf(msg, 0, sizeof(*ghdr));
ghdr->gh_version = PTLRPC_GSS_VERSION;
ghdr->gh_sp = (__u8) imp->imp_sec->ps_part;
ghdr->gh_flags = 0;
ghdr->gh_proc = PTLRPC_GSS_PROC_INIT;
ghdr->gh_seq = 0;
ghdr->gh_svc = SPTLRPC_SVC_NULL;
ghdr->gh_handle.len = 0;
/* fix the user desc */
if (req->rq_pack_udesc) {
ghdr->gh_flags |= LUSTRE_GSS_PACK_USER;
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];
LASSERT(p);
/* 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 = sec2gsec(req->rq_cli_ctx->cc_sec);
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) + cfs_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, int swabbed,
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, swabbed);
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 + cfs_size_round4(ghdr->gh_handle.len) +
cfs_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 */
int secid; /* 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 rc;
if (count != sizeof(param)) {
CERROR("ioctl size %lu, expect %lu, please check lgss_keyring "
"version\n", count, (unsigned long) sizeof(param));
return -EINVAL;
}
if (copy_from_user(&param, 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;
}
if (unlikely(!obd->obd_set_up)) {
CERROR("obd %s not setup\n", obdname);
return -EINVAL;
}
spin_lock(&obd->obd_dev_lock);
if (obd->obd_stopping) {
CERROR("obd %s has stopped\n", obdname);
spin_unlock(&obd->obd_dev_lock);
return -EINVAL;
}
if (strcmp(obd->obd_type->typ_name, LUSTRE_MDC_NAME) &&
strcmp(obd->obd_type->typ_name, LUSTRE_OSC_NAME) &&
strcmp(obd->obd_type->typ_name, LUSTRE_MGC_NAME)) {
CERROR("obd %s is not a client device\n", obdname);
spin_unlock(&obd->obd_dev_lock);
return -EINVAL;
}
spin_unlock(&obd->obd_dev_lock);
down_read(&obd->u.cli.cl_sem);
if (obd->u.cli.cl_import == NULL) {
CERROR("obd %s: import has gone\n", obd->obd_name);
up_read(&obd->u.cli.cl_sem);
return -EINVAL;
}
imp = class_import_get(obd->u.cli.cl_import);
up_read(&obd->u.cli.cl_sem);
if (imp->imp_deactive) {
CERROR("import has been deactivated\n");
class_import_put(imp);
return -EINVAL;
}
req = ptlrpc_request_alloc_pack(imp, &RQF_SEC_CTX, LUSTRE_OBD_VERSION,
SEC_CTX_INIT);
if (req == NULL) {
param.status = -ENOMEM;
goto out_copy;
}
if (req->rq_cli_ctx->cc_sec->ps_id != param.secid) {
CWARN("original secid %d, now has changed to %d, "
"cancel this negotiation\n", param.secid,
req->rq_cli_ctx->cc_sec->ps_id);
param.status = -EINVAL;
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;
}
ptlrpc_request_set_replen(req);
rc = ptlrpc_queue_wait(req);
if (rc) {
/* If any _real_ denial be made, we expect server return
* -EACCES reply or return success but indicate gss error
* inside reply message. All other errors are treated as
* timeout, caller might try the negotiation repeatedly,
* leave recovery decisions to general ptlrpc layer.
*
* FIXME maybe some other error code shouldn't be treated
* as timeout. */
param.status = rc;
if (rc != -EACCES)
param.status = -ETIMEDOUT;
goto out_copy;
}
LASSERT(req->rq_repdata);
lsize = ctx_init_parse_reply(req->rq_repdata,
ptlrpc_rep_need_swab(req),
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, &param, 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 rc;
LASSERT(atomic_read(&ctx->cc_refcount) > 0);
if (cli_ctx_is_error(ctx) || !cli_ctx_is_uptodate(ctx)) {
CDEBUG(D_SEC, "ctx %p(%u->%s) not uptodate, "
"don't send destroy rpc\n", ctx,
ctx->cc_vcred.vc_uid, sec2target_str(ctx->cc_sec));
return 0;
}
might_sleep();
CWARN("%s ctx %p idx "LPX64" (%u->%s)\n",
sec_is_reverse(ctx->cc_sec) ?
"server finishing reverse" : "client finishing forward",
ctx, gss_handle_to_u64(&gctx->gc_handle),
ctx->cc_vcred.vc_uid, sec2target_str(ctx->cc_sec));
gctx->gc_proc = PTLRPC_GSS_PROC_DESTROY;
req = ptlrpc_request_alloc(imp, &RQF_SEC_CTX);
if (req == NULL) {
CWARN("ctx %p(%u): fail to prepare rpc, destroy locally\n",
ctx, ctx->cc_vcred.vc_uid);
GOTO(out, rc = -ENOMEM);
}
rc = ptlrpc_request_bufs_pack(req, LUSTRE_OBD_VERSION, SEC_CTX_FINI,
NULL, ctx);
if (rc) {
ptlrpc_request_free(req);
GOTO(out_ref, rc);
}
/* fix the user desc */
if (req->rq_pack_udesc) {
/* 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_phase = RQ_PHASE_RPC;
rc = ptl_send_rpc(req, 1);
if (rc)
CWARN("ctx %p(%u->%s): rpc error %d, destroy locally\n", ctx,
ctx->cc_vcred.vc_uid, sec2target_str(ctx->cc_sec), rc);
out_ref:
ptlrpc_req_finished(req);
out:
return rc;
}
int __init gss_init_cli_upcall(void)
{
return 0;
}
void __exit gss_exit_cli_upcall(void)
{
}
/*
* Modifications for Lustre
*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Author: Eric Mei <ericm@clusterfs.com>
*/
/*
* 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 <andros@umich.edu>
*/
/*
* 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_ */
/*
* Modifications for Lustre
*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Copyright (c) 2011, Intel Corporation.
*
* Author: Eric Mei <ericm@clusterfs.com>
*/
/*
* 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 <andros@umich.edu>
*/
/*
* 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.
*/
#define DEBUG_SUBSYSTEM S_SEC
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <obd.h>
#include <obd_class.h>
#include <obd_support.h>
#include <lustre/lustre_idl.h>
#include <lustre_net.h>
#include <lustre_import.h>
#include <lustre_sec.h>
#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)
<length> possible multiple bytes, need to parse/generate
0x06 tag for OBJECT IDENTIFIER
<moid_length> compile-time constant string (assume 1 byte)
<moid_bytes> compile-time constant string
<inner_bytes> 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) {
sf &= 0x7f;
if (((*bufsize) - 1) < sf)
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;
toksize -= 1;
if (0 > toksize)
return (G_BAD_TOK_HEADER);
if (*buf++ != 0x60)
return (G_BAD_TOK_HEADER);
seqsize = der_read_length(&buf, &toksize);
if (seqsize < 0)
return(G_BAD_TOK_HEADER);
if (seqsize != toksize)
return (G_BAD_TOK_HEADER);
toksize -= 1;
if (0 > toksize)
return (G_BAD_TOK_HEADER);
if (*buf++ != 0x06)
return (G_BAD_TOK_HEADER);
toksize -= 1;
if (0 > toksize)
return (G_BAD_TOK_HEADER);
toid.len = *buf++;
toksize -= toid.len;
if (0 > toksize)
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
*/
toksize -= 2;
if (0 > toksize)
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;
len -= 1;
if (0 > len)
return (G_BAD_TOK_HEADER);
if (*buf++ != 0x60)
return (G_BAD_TOK_HEADER);
seqsize = der_read_length(&buf, &len);
if (seqsize < 0)
return (G_BAD_TOK_HEADER);
len -= 1;
if (0 > len)
return (G_BAD_TOK_HEADER);
if (*buf++ != 0x06)
return (G_BAD_TOK_HEADER);
len -= 1;
if (0 > len)
return (G_BAD_TOK_HEADER);
mech->len = *buf++;
len -= mech->len;
if (0 > len)
return (G_BAD_TOK_HEADER);
OBD_ALLOC_LARGE(mech->data, mech->len);
if (!mech->data)
return (G_BUFFER_ALLOC);
memcpy(mech->data, buf, mech->len);
return ret;
}
/*
* Modified from NFSv4 project for Lustre
*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Copyright (c) 2012, Intel Corporation.
*
* Author: Eric Mei <ericm@clusterfs.com>
*/
#ifndef __PTLRPC_GSS_GSS_INTERNAL_H_
#define __PTLRPC_GSS_GSS_INTERNAL_H_
#include <lustre_sec.h>
/*
* 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_empty(rawobj_t *obj);
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_extract_local_alloc(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);
int buffer_extract_bytes(const void **buf, __u32 *buflen,
void *res, __u32 reslen);
/*
* 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)
/*
* default gc interval
*/
#define GSS_GC_INTERVAL (60 * 60) /* 60 minutes */
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;
}
/*
* Max encryption element in block cipher algorithms.
*/
#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_tgt {
LUSTRE_GSS_TGT_MGS = 0,
LUSTRE_GSS_TGT_MDS = 1,
LUSTRE_GSS_TGT_OSS = 2,
};
enum ptlrpc_gss_header_flags {
LUSTRE_GSS_PACK_BULK = 1,
LUSTRE_GSS_PACK_USER = 2,
};
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_MGC_NAME))
return LUSTRE_GSS_TGT_MGS;
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;
}
/*
* following 3 header must have the same size and offset
*/
struct gss_header {
__u8 gh_version; /* gss version */
__u8 gh_sp; /* sec part */
__u16 gh_pad0;
__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 {
__u8 gh_version;
__u8 gh_sp;
__u16 gh_pad0;
__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 {
__u8 gh_version;
__u8 gh_sp;
__u16 gh_pad0;
__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_flags;
__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)
static inline __u64 gss_handle_to_u64(rawobj_t *handle)
{
if (handle->len != PTLRPC_GSS_MAX_HANDLE_SIZE)
return -1;
return *((__u64 *) handle->data);
}
#define GSS_SEQ_WIN (2048)
#define GSS_SEQ_WIN_MAIN GSS_SEQ_WIN
#define GSS_SEQ_WIN_BACK (128)
#define GSS_SEQ_REPACK_THRESHOLD (GSS_SEQ_WIN_MAIN / 2 + \
GSS_SEQ_WIN_MAIN / 4)
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 {
struct gss_ctx *gsc_mechctx;
struct gss_svc_seq_data gsc_seqdata;
rawobj_t gsc_rvs_hdl;
__u32 gsc_rvs_seq;
uid_t gsc_uid;
gid_t gsc_gid;
uid_t gsc_mapped_uid;
unsigned int gsc_usr_root:1,
gsc_usr_mds:1,
gsc_usr_oss:1,
gsc_remote:1,
gsc_reverse:1;
};
struct gss_svc_reqctx {
struct ptlrpc_svc_ctx src_base;
/*
* context
*/
struct gss_wire_ctx src_wirectx;
struct gss_svc_ctx *src_ctx;
/*
* record place of bulk_sec_desc in request/reply buffer
*/
struct ptlrpc_bulk_sec_desc *src_reqbsd;
int src_reqbsd_size;
struct ptlrpc_bulk_sec_desc *src_repbsd;
int src_repbsd_size;
/*
* flags
*/
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;
/* handle for the buddy svc ctx */
rawobj_t gc_svc_handle;
};
struct gss_cli_ctx_keyring {
struct gss_cli_ctx gck_base;
struct key *gck_key;
struct timer_list *gck_timer;
};
struct gss_sec {
struct ptlrpc_sec gs_base;
struct gss_api_mech *gs_mech;
spinlock_t gs_lock;
__u64 gs_rvs_hdl;
};
struct gss_sec_pipefs {
struct gss_sec gsp_base;
int gsp_chash_size; /* must be 2^n */
struct hlist_head gsp_chash[0];
};
/*
* FIXME cleanup the keyring upcall mutexes
*/
#define HAVE_KEYRING_UPCALL_SERIALIZED 1
struct gss_sec_keyring {
struct gss_sec gsk_base;
/*
* all contexts listed here. access is protected by sec spinlock.
*/
struct hlist_head gsk_clist;
/*
* specially point to root ctx (only one at a time). access is
* protected by sec spinlock.
*/
struct ptlrpc_cli_ctx *gsk_root_ctx;
/*
* specially serialize upcalls for root context.
*/
struct mutex gsk_root_uc_lock;
#ifdef HAVE_KEYRING_UPCALL_SERIALIZED
struct mutex gsk_uc_lock; /* serialize upcalls */
#endif
};
static inline struct gss_cli_ctx *ctx2gctx(struct ptlrpc_cli_ctx *ctx)
{
return container_of(ctx, struct gss_cli_ctx, gc_base);
}
static inline
struct gss_cli_ctx_keyring *ctx2gctx_keyring(struct ptlrpc_cli_ctx *ctx)
{
return container_of(ctx2gctx(ctx),
struct gss_cli_ctx_keyring, gck_base);
}
static inline struct gss_sec *sec2gsec(struct ptlrpc_sec *sec)
{
return container_of(sec, struct gss_sec, gs_base);
}
static inline struct gss_sec_pipefs *sec2gsec_pipefs(struct ptlrpc_sec *sec)
{
return container_of(sec2gsec(sec), struct gss_sec_pipefs, gsp_base);
}
static inline struct gss_sec_keyring *sec2gsec_keyring(struct ptlrpc_sec *sec)
{
return container_of(sec2gsec(sec), struct gss_sec_keyring, gsk_base);
}
#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);
}
static inline
struct gss_svc_ctx *gss_svc_ctx2gssctx(struct ptlrpc_svc_ctx *ctx)
{
LASSERT(ctx);
return gss_svc_ctx2reqctx(ctx)->src_ctx;
}
/* sec_gss.c */
int gss_cli_ctx_match(struct ptlrpc_cli_ctx *ctx, struct vfs_cred *vcred);
int gss_cli_ctx_display(struct ptlrpc_cli_ctx *ctx, char *buf, int bufsize);
int gss_cli_ctx_sign(struct ptlrpc_cli_ctx *ctx, struct ptlrpc_request *req);
int gss_cli_ctx_verify(struct ptlrpc_cli_ctx *ctx, struct ptlrpc_request *req);
int gss_cli_ctx_seal(struct ptlrpc_cli_ctx *ctx, struct ptlrpc_request *req);
int gss_cli_ctx_unseal(struct ptlrpc_cli_ctx *ctx, struct ptlrpc_request *req);
int gss_sec_install_rctx(struct obd_import *imp, struct ptlrpc_sec *sec,
struct ptlrpc_cli_ctx *ctx);
int gss_alloc_reqbuf(struct ptlrpc_sec *sec, struct ptlrpc_request *req,
int msgsize);
void gss_free_reqbuf(struct ptlrpc_sec *sec, struct ptlrpc_request *req);
int gss_alloc_repbuf(struct ptlrpc_sec *sec, struct ptlrpc_request *req,
int msgsize);
void gss_free_repbuf(struct ptlrpc_sec *sec, struct ptlrpc_request *req);
int gss_enlarge_reqbuf(struct ptlrpc_sec *sec, struct ptlrpc_request *req,
int segment, int newsize);
int gss_svc_accept(struct ptlrpc_sec_policy *policy,
struct ptlrpc_request *req);
void gss_svc_invalidate_ctx(struct ptlrpc_svc_ctx *svc_ctx);
int gss_svc_alloc_rs(struct ptlrpc_request *req, int msglen);
int gss_svc_authorize(struct ptlrpc_request *req);
void gss_svc_free_rs(struct ptlrpc_reply_state *rs);
void gss_svc_free_ctx(struct ptlrpc_svc_ctx *ctx);
int cli_ctx_expire(struct ptlrpc_cli_ctx *ctx);
int cli_ctx_check_death(struct ptlrpc_cli_ctx *ctx);
int gss_copy_rvc_cli_ctx(struct ptlrpc_cli_ctx *cli_ctx,
struct ptlrpc_svc_ctx *svc_ctx);
struct gss_header *gss_swab_header(struct lustre_msg *msg, int segment,
int swabbed);
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);
int gss_sec_create_common(struct gss_sec *gsec,
struct ptlrpc_sec_policy *policy,
struct obd_import *imp,
struct ptlrpc_svc_ctx *ctx,
struct sptlrpc_flavor *sf);
void gss_sec_destroy_common(struct gss_sec *gsec);
void gss_sec_kill(struct ptlrpc_sec *sec);
int gss_cli_ctx_init_common(struct ptlrpc_sec *sec,
struct ptlrpc_cli_ctx *ctx,
struct ptlrpc_ctx_ops *ctxops,
struct vfs_cred *vcred);
int gss_cli_ctx_fini_common(struct ptlrpc_sec *sec,
struct ptlrpc_cli_ctx *ctx);
void gss_cli_ctx_flags2str(unsigned long flags, char *buf, int bufsize);
/* gss_keyring.c */
int __init gss_init_keyring(void);
void __exit gss_exit_keyring(void);
/* gss_pipefs.c */
int __init gss_init_pipefs(void);
void __exit gss_exit_pipefs(void);
/* gss_bulk.c */
int gss_cli_prep_bulk(struct ptlrpc_request *req,
struct ptlrpc_bulk_desc *desc);
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_prep_bulk(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_cli_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 __init gss_init_cli_upcall(void);
void __exit gss_exit_cli_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_expire_rvs_ctx(rawobj_t *handle);
int gss_svc_upcall_dup_handle(rawobj_t *handle, struct gss_svc_ctx *ctx);
int gss_svc_upcall_update_sequence(rawobj_t *handle, __u32 seq);
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_init_svc_upcall(void);
void __exit gss_exit_svc_upcall(void);
/* lproc_gss.c */
void gss_stat_oos_record_cli(int behind);
void gss_stat_oos_record_svc(int phase, int replay);
int __init gss_init_lproc(void);
void __exit 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) {
CDEBUG(D_ERROR, "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';
LCONSOLE_INFO("DUMP %s@%p(%d): %s\n", name, ptr, size, buf);
OBD_FREE(buf, bufsize);
}
#endif /* __PTLRPC_GSS_GSS_INTERNAL_H_ */
/*
* GPL HEADER START
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 only,
* as published by the Free Software Foundation.
*
* This program 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 version 2 for more details (a copy is included
* in the LICENSE file that accompanied this code).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; If not, see
* http://www.sun.com/software/products/lustre/docs/GPLv2.pdf
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*
* GPL HEADER END
*/
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
* Use is subject to license terms.
*
* Copyright (c) 2012, Intel Corporation.
*/
/*
* This file is part of Lustre, http://www.lustre.org/
* Lustre is a trademark of Sun Microsystems, Inc.
*
* lustre/ptlrpc/gss/gss_keyring.c
*
* Author: Eric Mei <ericm@clusterfs.com>
*/
#define DEBUG_SUBSYSTEM S_SEC
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/dcache.h>
#include <linux/fs.h>
#include <linux/crypto.h>
#include <linux/key.h>
#include <linux/keyctl.h>
#include <linux/key-type.h>
#include <linux/mutex.h>
#include <asm/atomic.h>
#include <obd.h>
#include <obd_class.h>
#include <obd_support.h>
#include <lustre/lustre_idl.h>
#include <lustre_sec.h>
#include <lustre_net.h>
#include <lustre_import.h>
#include "gss_err.h"
#include "gss_internal.h"
#include "gss_api.h"
static struct ptlrpc_sec_policy gss_policy_keyring;
static struct ptlrpc_ctx_ops gss_keyring_ctxops;
static struct key_type gss_key_type;
static int sec_install_rctx_kr(struct ptlrpc_sec *sec,
struct ptlrpc_svc_ctx *svc_ctx);
/*
* the timeout is only for the case that upcall child process die abnormally.
* in any other cases it should finally update kernel key.
*
* FIXME we'd better to incorporate the client & server side upcall timeouts
* into the framework of Adaptive Timeouts, but we need to figure out how to
* make sure that kernel knows the upcall processes is in-progress or died
* unexpectedly.
*/
#define KEYRING_UPCALL_TIMEOUT (obd_timeout + obd_timeout)
/****************************************
* internal helpers *
****************************************/
#define DUMP_PROCESS_KEYRINGS(tsk) \
{ \
CWARN("DUMP PK: %s[%u,%u/%u](<-%s[%u,%u/%u]): " \
"a %d, t %d, p %d, s %d, u %d, us %d, df %d\n", \
tsk->comm, tsk->pid, tsk->uid, tsk->fsuid, \
tsk->parent->comm, tsk->parent->pid, \
tsk->parent->uid, tsk->parent->fsuid, \
tsk->request_key_auth ? \
tsk->request_key_auth->serial : 0, \
key_cred(tsk)->thread_keyring ? \
key_cred(tsk)->thread_keyring->serial : 0, \
key_tgcred(tsk)->process_keyring ? \
key_tgcred(tsk)->process_keyring->serial : 0, \
key_tgcred(tsk)->session_keyring ? \
key_tgcred(tsk)->session_keyring->serial : 0, \
key_cred(tsk)->user->uid_keyring ? \
key_cred(tsk)->user->uid_keyring->serial : 0, \
key_cred(tsk)->user->session_keyring ? \
key_cred(tsk)->user->session_keyring->serial : 0, \
key_cred(tsk)->jit_keyring \
); \
}
#define DUMP_KEY(key) \
{ \
CWARN("DUMP KEY: %p(%d) ref %d u%u/g%u desc %s\n", \
key, key->serial, atomic_read(&key->usage), \
key->uid, key->gid, \
key->description ? key->description : "n/a" \
); \
}
#define key_cred(tsk) ((tsk)->cred)
#define key_tgcred(tsk) ((tsk)->cred->tgcred)
static inline void keyring_upcall_lock(struct gss_sec_keyring *gsec_kr)
{
#ifdef HAVE_KEYRING_UPCALL_SERIALIZED
mutex_lock(&gsec_kr->gsk_uc_lock);
#endif
}
static inline void keyring_upcall_unlock(struct gss_sec_keyring *gsec_kr)
{
#ifdef HAVE_KEYRING_UPCALL_SERIALIZED
mutex_unlock(&gsec_kr->gsk_uc_lock);
#endif
}
static inline void key_revoke_locked(struct key *key)
{
set_bit(KEY_FLAG_REVOKED, &key->flags);
}
static void ctx_upcall_timeout_kr(unsigned long data)
{
struct ptlrpc_cli_ctx *ctx = (struct ptlrpc_cli_ctx *) data;
struct key *key = ctx2gctx_keyring(ctx)->gck_key;
CWARN("ctx %p, key %p\n", ctx, key);
LASSERT(key);
cli_ctx_expire(ctx);
key_revoke_locked(key);
}
static
void ctx_start_timer_kr(struct ptlrpc_cli_ctx *ctx, long timeout)
{
struct gss_cli_ctx_keyring *gctx_kr = ctx2gctx_keyring(ctx);
struct timer_list *timer = gctx_kr->gck_timer;
LASSERT(timer);
CDEBUG(D_SEC, "ctx %p: start timer %lds\n", ctx, timeout);
timeout = timeout * HZ + cfs_time_current();
init_timer(timer);
timer->expires = timeout;
timer->data = (unsigned long) ctx;
timer->function = ctx_upcall_timeout_kr;
add_timer(timer);
}
/*
* caller should make sure no race with other threads
*/
static
void ctx_clear_timer_kr(struct ptlrpc_cli_ctx *ctx)
{
struct gss_cli_ctx_keyring *gctx_kr = ctx2gctx_keyring(ctx);
struct timer_list *timer = gctx_kr->gck_timer;
if (timer == NULL)
return;
CDEBUG(D_SEC, "ctx %p, key %p\n", ctx, gctx_kr->gck_key);
gctx_kr->gck_timer = NULL;
del_singleshot_timer_sync(timer);
OBD_FREE_PTR(timer);
}
static
struct ptlrpc_cli_ctx *ctx_create_kr(struct ptlrpc_sec *sec,
struct vfs_cred *vcred)
{
struct ptlrpc_cli_ctx *ctx;
struct gss_cli_ctx_keyring *gctx_kr;
OBD_ALLOC_PTR(gctx_kr);
if (gctx_kr == NULL)
return NULL;
OBD_ALLOC_PTR(gctx_kr->gck_timer);
if (gctx_kr->gck_timer == NULL) {
OBD_FREE_PTR(gctx_kr);
return NULL;
}
init_timer(gctx_kr->gck_timer);
ctx = &gctx_kr->gck_base.gc_base;
if (gss_cli_ctx_init_common(sec, ctx, &gss_keyring_ctxops, vcred)) {
OBD_FREE_PTR(gctx_kr->gck_timer);
OBD_FREE_PTR(gctx_kr);
return NULL;
}
ctx->cc_expire = cfs_time_current_sec() + KEYRING_UPCALL_TIMEOUT;
clear_bit(PTLRPC_CTX_NEW_BIT, &ctx->cc_flags);
atomic_inc(&ctx->cc_refcount); /* for the caller */
return ctx;
}
static void ctx_destroy_kr(struct ptlrpc_cli_ctx *ctx)
{
struct ptlrpc_sec *sec = ctx->cc_sec;
struct gss_cli_ctx_keyring *gctx_kr = ctx2gctx_keyring(ctx);
CDEBUG(D_SEC, "destroying ctx %p\n", ctx);
/* at this time the association with key has been broken. */
LASSERT(sec);
LASSERT(atomic_read(&sec->ps_refcount) > 0);
LASSERT(atomic_read(&sec->ps_nctx) > 0);
LASSERT(test_bit(PTLRPC_CTX_CACHED_BIT, &ctx->cc_flags) == 0);
LASSERT(gctx_kr->gck_key == NULL);
ctx_clear_timer_kr(ctx);
LASSERT(gctx_kr->gck_timer == NULL);
if (gss_cli_ctx_fini_common(sec, ctx))
return;
OBD_FREE_PTR(gctx_kr);
atomic_dec(&sec->ps_nctx);
sptlrpc_sec_put(sec);
}
static void ctx_release_kr(struct ptlrpc_cli_ctx *ctx, int sync)
{
if (sync) {
ctx_destroy_kr(ctx);
} else {
atomic_inc(&ctx->cc_refcount);
sptlrpc_gc_add_ctx(ctx);
}
}
static void ctx_put_kr(struct ptlrpc_cli_ctx *ctx, int sync)
{
LASSERT(atomic_read(&ctx->cc_refcount) > 0);
if (atomic_dec_and_test(&ctx->cc_refcount))
ctx_release_kr(ctx, sync);
}
/*
* key <-> ctx association and rules:
* - ctx might not bind with any key
* - key/ctx binding is protected by key semaphore (if the key present)
* - key and ctx each take a reference of the other
* - ctx enlist/unlist is protected by ctx spinlock
* - never enlist a ctx after it's been unlisted
* - whoever do enlist should also do bind, lock key before enlist:
* - lock key -> lock ctx -> enlist -> unlock ctx -> bind -> unlock key
* - whoever do unlist should also do unbind:
* - lock key -> lock ctx -> unlist -> unlock ctx -> unbind -> unlock key
* - lock ctx -> unlist -> unlock ctx -> lock key -> unbind -> unlock key
*/
static inline void spin_lock_if(spinlock_t *lock, int condition)
{
if (condition)
spin_lock(lock);
}
static inline void spin_unlock_if(spinlock_t *lock, int condition)
{
if (condition)
spin_unlock(lock);
}
static void ctx_enlist_kr(struct ptlrpc_cli_ctx *ctx, int is_root, int locked)
{
struct ptlrpc_sec *sec = ctx->cc_sec;
struct gss_sec_keyring *gsec_kr = sec2gsec_keyring(sec);
LASSERT(!test_bit(PTLRPC_CTX_CACHED_BIT, &ctx->cc_flags));
LASSERT(atomic_read(&ctx->cc_refcount) > 0);
spin_lock_if(&sec->ps_lock, !locked);
atomic_inc(&ctx->cc_refcount);
set_bit(PTLRPC_CTX_CACHED_BIT, &ctx->cc_flags);
hlist_add_head(&ctx->cc_cache, &gsec_kr->gsk_clist);
if (is_root)
gsec_kr->gsk_root_ctx = ctx;
spin_unlock_if(&sec->ps_lock, !locked);
}
/*
* Note after this get called, caller should not access ctx again because
* it might have been freed, unless caller hold at least one refcount of
* the ctx.
*
* return non-zero if we indeed unlist this ctx.
*/
static int ctx_unlist_kr(struct ptlrpc_cli_ctx *ctx, int locked)
{
struct ptlrpc_sec *sec = ctx->cc_sec;
struct gss_sec_keyring *gsec_kr = sec2gsec_keyring(sec);
/* if hashed bit has gone, leave the job to somebody who is doing it */
if (test_and_clear_bit(PTLRPC_CTX_CACHED_BIT, &ctx->cc_flags) == 0)
return 0;
/* drop ref inside spin lock to prevent race with other operations */
spin_lock_if(&sec->ps_lock, !locked);
if (gsec_kr->gsk_root_ctx == ctx)
gsec_kr->gsk_root_ctx = NULL;
hlist_del_init(&ctx->cc_cache);
atomic_dec(&ctx->cc_refcount);
spin_unlock_if(&sec->ps_lock, !locked);
return 1;
}
/*
* bind a key with a ctx together.
* caller must hold write lock of the key, as well as ref on key & ctx.
*/
static void bind_key_ctx(struct key *key, struct ptlrpc_cli_ctx *ctx)
{
LASSERT(atomic_read(&ctx->cc_refcount) > 0);
LASSERT(atomic_read(&key->usage) > 0);
LASSERT(ctx2gctx_keyring(ctx)->gck_key == NULL);
LASSERT(key->payload.data == NULL);
/* at this time context may or may not in list. */
key_get(key);
atomic_inc(&ctx->cc_refcount);
ctx2gctx_keyring(ctx)->gck_key = key;
key->payload.data = ctx;
}
/*
* unbind a key and a ctx.
* caller must hold write lock, as well as a ref of the key.
*/
static void unbind_key_ctx(struct key *key, struct ptlrpc_cli_ctx *ctx)
{
LASSERT(key->payload.data == ctx);
LASSERT(test_bit(PTLRPC_CTX_CACHED_BIT, &ctx->cc_flags) == 0);
/* must revoke the key, or others may treat it as newly created */
key_revoke_locked(key);
key->payload.data = NULL;
ctx2gctx_keyring(ctx)->gck_key = NULL;
/* once ctx get split from key, the timer is meaningless */
ctx_clear_timer_kr(ctx);
ctx_put_kr(ctx, 1);
key_put(key);
}
/*
* given a ctx, unbind with its coupled key, if any.
* unbind could only be called once, so we don't worry the key be released
* by someone else.
*/
static void unbind_ctx_kr(struct ptlrpc_cli_ctx *ctx)
{
struct key *key = ctx2gctx_keyring(ctx)->gck_key;
if (key) {
LASSERT(key->payload.data == ctx);
key_get(key);
down_write(&key->sem);
unbind_key_ctx(key, ctx);
up_write(&key->sem);
key_put(key);
}
}
/*
* given a key, unbind with its coupled ctx, if any.
* caller must hold write lock, as well as a ref of the key.
*/
static void unbind_key_locked(struct key *key)
{
struct ptlrpc_cli_ctx *ctx = key->payload.data;
if (ctx)
unbind_key_ctx(key, ctx);
}
/*
* unlist a ctx, and unbind from coupled key
*/
static void kill_ctx_kr(struct ptlrpc_cli_ctx *ctx)
{
if (ctx_unlist_kr(ctx, 0))
unbind_ctx_kr(ctx);
}
/*
* given a key, unlist and unbind with the coupled ctx (if any).
* caller must hold write lock, as well as a ref of the key.
*/
static void kill_key_locked(struct key *key)
{
struct ptlrpc_cli_ctx *ctx = key->payload.data;
if (ctx && ctx_unlist_kr(ctx, 0))
unbind_key_locked(key);
}
/*
* caller should hold one ref on contexts in freelist.
*/
static void dispose_ctx_list_kr(struct hlist_head *freelist)
{
struct hlist_node *next;
struct ptlrpc_cli_ctx *ctx;
struct gss_cli_ctx *gctx;
hlist_for_each_entry_safe(ctx, next, freelist, cc_cache) {
hlist_del_init(&ctx->cc_cache);
/* reverse ctx: update current seq to buddy svcctx if exist.
* ideally this should be done at gss_cli_ctx_finalize(), but
* the ctx destroy could be delayed by:
* 1) ctx still has reference;
* 2) ctx destroy is asynchronous;
* and reverse import call inval_all_ctx() require this be done
*_immediately_ otherwise newly created reverse ctx might copy
* the very old sequence number from svcctx. */
gctx = ctx2gctx(ctx);
if (!rawobj_empty(&gctx->gc_svc_handle) &&
sec_is_reverse(gctx->gc_base.cc_sec)) {
gss_svc_upcall_update_sequence(&gctx->gc_svc_handle,
(__u32) atomic_read(&gctx->gc_seq));
}
/* we need to wakeup waiting reqs here. the context might
* be forced released before upcall finished, then the
* late-arrived downcall can't find the ctx even. */
sptlrpc_cli_ctx_wakeup(ctx);
unbind_ctx_kr(ctx);
ctx_put_kr(ctx, 0);
}
}
/*
* lookup a root context directly in a sec, return root ctx with a
* reference taken or NULL.
*/
static
struct ptlrpc_cli_ctx * sec_lookup_root_ctx_kr(struct ptlrpc_sec *sec)
{
struct gss_sec_keyring *gsec_kr = sec2gsec_keyring(sec);
struct ptlrpc_cli_ctx *ctx = NULL;
spin_lock(&sec->ps_lock);
ctx = gsec_kr->gsk_root_ctx;
if (ctx == NULL && unlikely(sec_is_reverse(sec))) {
struct ptlrpc_cli_ctx *tmp;
/* reverse ctx, search root ctx in list, choose the one
* with shortest expire time, which is most possibly have
* an established peer ctx at client side. */
hlist_for_each_entry(tmp, &gsec_kr->gsk_clist, cc_cache) {
if (ctx == NULL || ctx->cc_expire == 0 ||
ctx->cc_expire > tmp->cc_expire) {
ctx = tmp;
/* promote to be root_ctx */
gsec_kr->gsk_root_ctx = ctx;
}
}
}
if (ctx) {
LASSERT(atomic_read(&ctx->cc_refcount) > 0);
LASSERT(!hlist_empty(&gsec_kr->gsk_clist));
atomic_inc(&ctx->cc_refcount);
}
spin_unlock(&sec->ps_lock);
return ctx;
}
#define RVS_CTX_EXPIRE_NICE (10)
static
void rvs_sec_install_root_ctx_kr(struct ptlrpc_sec *sec,
struct ptlrpc_cli_ctx *new_ctx,
struct key *key)
{
struct gss_sec_keyring *gsec_kr = sec2gsec_keyring(sec);
struct ptlrpc_cli_ctx *ctx;
cfs_time_t now;
LASSERT(sec_is_reverse(sec));
spin_lock(&sec->ps_lock);
now = cfs_time_current_sec();
/* set all existing ctxs short expiry */
hlist_for_each_entry(ctx, &gsec_kr->gsk_clist, cc_cache) {
if (ctx->cc_expire > now + RVS_CTX_EXPIRE_NICE) {
ctx->cc_early_expire = 1;
ctx->cc_expire = now + RVS_CTX_EXPIRE_NICE;
}
}
/* if there's root_ctx there, instead obsolete the current
* immediately, we leave it continue operating for a little while.
* hopefully when the first backward rpc with newest ctx send out,
* the client side already have the peer ctx well established. */
ctx_enlist_kr(new_ctx, gsec_kr->gsk_root_ctx ? 0 : 1, 1);
if (key)
bind_key_ctx(key, new_ctx);
spin_unlock(&sec->ps_lock);
}
static void construct_key_desc(void *buf, int bufsize,
struct ptlrpc_sec *sec, uid_t uid)
{
snprintf(buf, bufsize, "%d@%x", uid, sec->ps_id);
((char *)buf)[bufsize - 1] = '\0';
}
/****************************************
* sec apis *
****************************************/
static
struct ptlrpc_sec * gss_sec_create_kr(struct obd_import *imp,
struct ptlrpc_svc_ctx *svcctx,
struct sptlrpc_flavor *sf)
{
struct gss_sec_keyring *gsec_kr;
OBD_ALLOC(gsec_kr, sizeof(*gsec_kr));
if (gsec_kr == NULL)
return NULL;
INIT_HLIST_HEAD(&gsec_kr->gsk_clist);
gsec_kr->gsk_root_ctx = NULL;
mutex_init(&gsec_kr->gsk_root_uc_lock);
#ifdef HAVE_KEYRING_UPCALL_SERIALIZED
mutex_init(&gsec_kr->gsk_uc_lock);
#endif
if (gss_sec_create_common(&gsec_kr->gsk_base, &gss_policy_keyring,
imp, svcctx, sf))
goto err_free;
if (svcctx != NULL &&
sec_install_rctx_kr(&gsec_kr->gsk_base.gs_base, svcctx)) {
gss_sec_destroy_common(&gsec_kr->gsk_base);
goto err_free;
}
return &gsec_kr->gsk_base.gs_base;
err_free:
OBD_FREE(gsec_kr, sizeof(*gsec_kr));
return NULL;
}
static
void gss_sec_destroy_kr(struct ptlrpc_sec *sec)
{
struct gss_sec *gsec = sec2gsec(sec);
struct gss_sec_keyring *gsec_kr = sec2gsec_keyring(sec);
CDEBUG(D_SEC, "destroy %s@%p\n", sec->ps_policy->sp_name, sec);
LASSERT(hlist_empty(&gsec_kr->gsk_clist));
LASSERT(gsec_kr->gsk_root_ctx == NULL);
gss_sec_destroy_common(gsec);
OBD_FREE(gsec_kr, sizeof(*gsec_kr));
}
static inline int user_is_root(struct ptlrpc_sec *sec, struct vfs_cred *vcred)
{
/* except the ROOTONLY flag, treat it as root user only if real uid
* is 0, euid/fsuid being 0 are handled as setuid scenarios */
if (sec_is_rootonly(sec) || (vcred->vc_uid == 0))
return 1;
else
return 0;
}
/*
* unlink request key from it's ring, which is linked during request_key().
* sadly, we have to 'guess' which keyring it's linked to.
*
* FIXME this code is fragile, depend on how request_key_link() is implemented.
*/
static void request_key_unlink(struct key *key)
{
struct task_struct *tsk = current;
struct key *ring;
switch (key_cred(tsk)->jit_keyring) {
case KEY_REQKEY_DEFL_DEFAULT:
case KEY_REQKEY_DEFL_THREAD_KEYRING:
ring = key_get(key_cred(tsk)->thread_keyring);
if (ring)
break;
case KEY_REQKEY_DEFL_PROCESS_KEYRING:
ring = key_get(key_tgcred(tsk)->process_keyring);
if (ring)
break;
case KEY_REQKEY_DEFL_SESSION_KEYRING:
rcu_read_lock();
ring = key_get(rcu_dereference(key_tgcred(tsk)
->session_keyring));
rcu_read_unlock();
if (ring)
break;
case KEY_REQKEY_DEFL_USER_SESSION_KEYRING:
ring = key_get(key_cred(tsk)->user->session_keyring);
break;
case KEY_REQKEY_DEFL_USER_KEYRING:
ring = key_get(key_cred(tsk)->user->uid_keyring);
break;
case KEY_REQKEY_DEFL_GROUP_KEYRING:
default:
LBUG();
}
LASSERT(ring);
key_unlink(ring, key);
key_put(ring);
}
static
struct ptlrpc_cli_ctx * gss_sec_lookup_ctx_kr(struct ptlrpc_sec *sec,
struct vfs_cred *vcred,
int create, int remove_dead)
{
struct obd_import *imp = sec->ps_import;
struct gss_sec_keyring *gsec_kr = sec2gsec_keyring(sec);
struct ptlrpc_cli_ctx *ctx = NULL;
unsigned int is_root = 0, create_new = 0;
struct key *key;
char desc[24];
char *coinfo;
int coinfo_size;
char *co_flags = "";
LASSERT(imp != NULL);
is_root = user_is_root(sec, vcred);
/* a little bit optimization for root context */
if (is_root) {
ctx = sec_lookup_root_ctx_kr(sec);
/*
* Only lookup directly for REVERSE sec, which should
* always succeed.
*/
if (ctx || sec_is_reverse(sec))
return ctx;
}
LASSERT(create != 0);
/* for root context, obtain lock and check again, this time hold
* the root upcall lock, make sure nobody else populated new root
* context after last check. */
if (is_root) {
mutex_lock(&gsec_kr->gsk_root_uc_lock);
ctx = sec_lookup_root_ctx_kr(sec);
if (ctx)
goto out;
/* update reverse handle for root user */
sec2gsec(sec)->gs_rvs_hdl = gss_get_next_ctx_index();
switch (sec->ps_part) {
case LUSTRE_SP_MDT:
co_flags = "m";
break;
case LUSTRE_SP_OST:
co_flags = "o";
break;
case LUSTRE_SP_MGC:
co_flags = "rmo";
break;
case LUSTRE_SP_CLI:
co_flags = "r";
break;
case LUSTRE_SP_MGS:
default:
LBUG();
}
}
/* in case of setuid, key will be constructed as owner of fsuid/fsgid,
* but we do authentication based on real uid/gid. the key permission
* bits will be exactly as POS_ALL, so only processes who subscribed
* this key could have the access, although the quota might be counted
* on others (fsuid/fsgid).
*
* keyring will use fsuid/fsgid as upcall parameters, so we have to
* encode real uid/gid into callout info.
*/
construct_key_desc(desc, sizeof(desc), sec, vcred->vc_uid);
/* callout info format:
* secid:mech:uid:gid:flags:svc_type:peer_nid:target_uuid
*/
coinfo_size = sizeof(struct obd_uuid) + MAX_OBD_NAME + 64;
OBD_ALLOC(coinfo, coinfo_size);
if (coinfo == NULL)
goto out;
snprintf(coinfo, coinfo_size, "%d:%s:%u:%u:%s:%d:"LPX64":%s",
sec->ps_id, sec2gsec(sec)->gs_mech->gm_name,
vcred->vc_uid, vcred->vc_gid,
co_flags, import_to_gss_svc(imp),
imp->imp_connection->c_peer.nid, imp->imp_obd->obd_name);
CDEBUG(D_SEC, "requesting key for %s\n", desc);
keyring_upcall_lock(gsec_kr);
key = request_key(&gss_key_type, desc, coinfo);
keyring_upcall_unlock(gsec_kr);
OBD_FREE(coinfo, coinfo_size);
if (IS_ERR(key)) {
CERROR("failed request key: %ld\n", PTR_ERR(key));
goto out;
}
CDEBUG(D_SEC, "obtained key %08x for %s\n", key->serial, desc);
/* once payload.data was pointed to a ctx, it never changes until
* we de-associate them; but parallel request_key() may return
* a key with payload.data == NULL at the same time. so we still
* need wirtelock of key->sem to serialize them. */
down_write(&key->sem);
if (likely(key->payload.data != NULL)) {
ctx = key->payload.data;
LASSERT(atomic_read(&ctx->cc_refcount) >= 1);
LASSERT(ctx2gctx_keyring(ctx)->gck_key == key);
LASSERT(atomic_read(&key->usage) >= 2);
/* simply take a ref and return. it's upper layer's
* responsibility to detect & replace dead ctx. */
atomic_inc(&ctx->cc_refcount);
} else {
/* pre initialization with a cli_ctx. this can't be done in
* key_instantiate() because we'v no enough information
* there. */
ctx = ctx_create_kr(sec, vcred);
if (ctx != NULL) {
ctx_enlist_kr(ctx, is_root, 0);
bind_key_ctx(key, ctx);
ctx_start_timer_kr(ctx, KEYRING_UPCALL_TIMEOUT);
CDEBUG(D_SEC, "installed key %p <-> ctx %p (sec %p)\n",
key, ctx, sec);
} else {
/* we'd prefer to call key_revoke(), but we more like
* to revoke it within this key->sem locked period. */
key_revoke_locked(key);
}
create_new = 1;
}
up_write(&key->sem);
if (is_root && create_new)
request_key_unlink(key);
key_put(key);
out:
if (is_root)
mutex_unlock(&gsec_kr->gsk_root_uc_lock);
return ctx;
}
static
void gss_sec_release_ctx_kr(struct ptlrpc_sec *sec,
struct ptlrpc_cli_ctx *ctx,
int sync)
{
LASSERT(atomic_read(&sec->ps_refcount) > 0);
LASSERT(atomic_read(&ctx->cc_refcount) == 0);
ctx_release_kr(ctx, sync);
}
/*
* flush context of normal user, we must resort to keyring itself to find out
* contexts which belong to me.
*
* Note here we suppose only to flush _my_ context, the "uid" will
* be ignored in the search.
*/
static
void flush_user_ctx_cache_kr(struct ptlrpc_sec *sec,
uid_t uid,
int grace, int force)
{
struct key *key;
char desc[24];
/* nothing to do for reverse or rootonly sec */
if (sec_is_reverse(sec) || sec_is_rootonly(sec))
return;
construct_key_desc(desc, sizeof(desc), sec, uid);
/* there should be only one valid key, but we put it in the
* loop in case of any weird cases */
for (;;) {
key = request_key(&gss_key_type, desc, NULL);
if (IS_ERR(key)) {
CDEBUG(D_SEC, "No more key found for current user\n");
break;
}
down_write(&key->sem);
kill_key_locked(key);
/* kill_key_locked() should usually revoke the key, but we
* revoke it again to make sure, e.g. some case the key may
* not well coupled with a context. */
key_revoke_locked(key);
up_write(&key->sem);
key_put(key);
}
}
/*
* flush context of root or all, we iterate through the list.
*/
static
void flush_spec_ctx_cache_kr(struct ptlrpc_sec *sec,
uid_t uid,
int grace, int force)
{
struct gss_sec_keyring *gsec_kr;
struct hlist_head freelist = HLIST_HEAD_INIT;
struct hlist_node *next;
struct ptlrpc_cli_ctx *ctx;
gsec_kr = sec2gsec_keyring(sec);
spin_lock(&sec->ps_lock);
hlist_for_each_entry_safe(ctx, next,
&gsec_kr->gsk_clist, cc_cache) {
LASSERT(atomic_read(&ctx->cc_refcount) > 0);
if (uid != -1 && uid != ctx->cc_vcred.vc_uid)
continue;
/* at this moment there's at least 2 base reference:
* key association and in-list. */
if (atomic_read(&ctx->cc_refcount) > 2) {
if (!force)
continue;
CWARN("flush busy ctx %p(%u->%s, extra ref %d)\n",
ctx, ctx->cc_vcred.vc_uid,
sec2target_str(ctx->cc_sec),
atomic_read(&ctx->cc_refcount) - 2);
}
set_bit(PTLRPC_CTX_DEAD_BIT, &ctx->cc_flags);
if (!grace)
clear_bit(PTLRPC_CTX_UPTODATE_BIT, &ctx->cc_flags);
atomic_inc(&ctx->cc_refcount);
if (ctx_unlist_kr(ctx, 1)) {
hlist_add_head(&ctx->cc_cache, &freelist);
} else {
LASSERT(atomic_read(&ctx->cc_refcount) >= 2);
atomic_dec(&ctx->cc_refcount);
}
}
spin_unlock(&sec->ps_lock);
dispose_ctx_list_kr(&freelist);
}
static
int gss_sec_flush_ctx_cache_kr(struct ptlrpc_sec *sec,
uid_t uid, int grace, int force)
{
CDEBUG(D_SEC, "sec %p(%d, nctx %d), uid %d, grace %d, force %d\n",
sec, atomic_read(&sec->ps_refcount),
atomic_read(&sec->ps_nctx),
uid, grace, force);
if (uid != -1 && uid != 0)
flush_user_ctx_cache_kr(sec, uid, grace, force);
else
flush_spec_ctx_cache_kr(sec, uid, grace, force);
return 0;
}
static
void gss_sec_gc_ctx_kr(struct ptlrpc_sec *sec)
{
struct gss_sec_keyring *gsec_kr = sec2gsec_keyring(sec);
struct hlist_head freelist = HLIST_HEAD_INIT;
struct hlist_node *next;
struct ptlrpc_cli_ctx *ctx;
CWARN("running gc\n");
spin_lock(&sec->ps_lock);
hlist_for_each_entry_safe(ctx, next,
&gsec_kr->gsk_clist, cc_cache) {
LASSERT(atomic_read(&ctx->cc_refcount) > 0);
atomic_inc(&ctx->cc_refcount);
if (cli_ctx_check_death(ctx) && ctx_unlist_kr(ctx, 1)) {
hlist_add_head(&ctx->cc_cache, &freelist);
CWARN("unhashed ctx %p\n", ctx);
} else {
LASSERT(atomic_read(&ctx->cc_refcount) >= 2);
atomic_dec(&ctx->cc_refcount);
}
}
spin_unlock(&sec->ps_lock);
dispose_ctx_list_kr(&freelist);
}
static
int gss_sec_display_kr(struct ptlrpc_sec *sec, struct seq_file *seq)
{
struct gss_sec_keyring *gsec_kr = sec2gsec_keyring(sec);
struct hlist_node *next;
struct ptlrpc_cli_ctx *ctx;
struct gss_cli_ctx *gctx;
time_t now = cfs_time_current_sec();
spin_lock(&sec->ps_lock);
hlist_for_each_entry_safe(ctx, next,
&gsec_kr->gsk_clist, cc_cache) {
struct key *key;
char flags_str[40];
char mech[40];
gctx = ctx2gctx(ctx);
key = ctx2gctx_keyring(ctx)->gck_key;
gss_cli_ctx_flags2str(ctx->cc_flags,
flags_str, sizeof(flags_str));
if (gctx->gc_mechctx)
lgss_display(gctx->gc_mechctx, mech, sizeof(mech));
else
snprintf(mech, sizeof(mech), "N/A");
mech[sizeof(mech) - 1] = '\0';
seq_printf(seq, "%p: uid %u, ref %d, expire %ld(%+ld), fl %s, "
"seq %d, win %u, key %08x(ref %d), "
"hdl "LPX64":"LPX64", mech: %s\n",
ctx, ctx->cc_vcred.vc_uid,
atomic_read(&ctx->cc_refcount),
ctx->cc_expire,
ctx->cc_expire ? ctx->cc_expire - now : 0,
flags_str,
atomic_read(&gctx->gc_seq),
gctx->gc_win,
key ? key->serial : 0,
key ? atomic_read(&key->usage) : 0,
gss_handle_to_u64(&gctx->gc_handle),
gss_handle_to_u64(&gctx->gc_svc_handle),
mech);
}
spin_unlock(&sec->ps_lock);
return 0;
}
/****************************************
* cli_ctx apis *
****************************************/
static
int gss_cli_ctx_refresh_kr(struct ptlrpc_cli_ctx *ctx)
{
/* upcall is already on the way */
return 0;
}
static
int gss_cli_ctx_validate_kr(struct ptlrpc_cli_ctx *ctx)
{
LASSERT(atomic_read(&ctx->cc_refcount) > 0);
LASSERT(ctx->cc_sec);
if (cli_ctx_check_death(ctx)) {
kill_ctx_kr(ctx);
return 1;
}
if (cli_ctx_is_ready(ctx))
return 0;
return 1;
}
static
void gss_cli_ctx_die_kr(struct ptlrpc_cli_ctx *ctx, int grace)
{
LASSERT(atomic_read(&ctx->cc_refcount) > 0);
LASSERT(ctx->cc_sec);
cli_ctx_expire(ctx);
kill_ctx_kr(ctx);
}
/****************************************
* (reverse) service *
****************************************/
/*
* reverse context could have nothing to do with keyrings. here we still keep
* the version which bind to a key, for future reference.
*/
#define HAVE_REVERSE_CTX_NOKEY
static
int sec_install_rctx_kr(struct ptlrpc_sec *sec,
struct ptlrpc_svc_ctx *svc_ctx)
{
struct ptlrpc_cli_ctx *cli_ctx;
struct vfs_cred vcred = { 0, 0 };
int rc;
LASSERT(sec);
LASSERT(svc_ctx);
cli_ctx = ctx_create_kr(sec, &vcred);
if (cli_ctx == NULL)
return -ENOMEM;
rc = gss_copy_rvc_cli_ctx(cli_ctx, svc_ctx);
if (rc) {
CERROR("failed copy reverse cli ctx: %d\n", rc);
ctx_put_kr(cli_ctx, 1);
return rc;
}
rvs_sec_install_root_ctx_kr(sec, cli_ctx, NULL);
ctx_put_kr(cli_ctx, 1);
return 0;
}
/****************************************
* service apis *
****************************************/
static
int gss_svc_accept_kr(struct ptlrpc_request *req)
{
return gss_svc_accept(&gss_policy_keyring, req);
}
static
int gss_svc_install_rctx_kr(struct obd_import *imp,
struct ptlrpc_svc_ctx *svc_ctx)
{
struct ptlrpc_sec *sec;
int rc;
sec = sptlrpc_import_sec_ref(imp);
LASSERT(sec);
rc = sec_install_rctx_kr(sec, svc_ctx);
sptlrpc_sec_put(sec);
return rc;
}
/****************************************
* key apis *
****************************************/
static
int gss_kt_instantiate(struct key *key, const void *data, size_t datalen)
{
int rc;
if (data != NULL || datalen != 0) {
CERROR("invalid: data %p, len %lu\n", data, (long)datalen);
return -EINVAL;
}
if (key->payload.data != 0) {
CERROR("key already have payload\n");
return -EINVAL;
}
/* link the key to session keyring, so following context negotiation
* rpc fired from user space could find this key. This will be unlinked
* automatically when upcall processes die.
*
* we can't do this through keyctl from userspace, because the upcall
* might be neither possessor nor owner of the key (setuid).
*
* the session keyring is created upon upcall, and don't change all
* the way until upcall finished, so rcu lock is not needed here.
*/
LASSERT(key_tgcred(current)->session_keyring);
lockdep_off();
rc = key_link(key_tgcred(current)->session_keyring, key);
lockdep_on();
if (unlikely(rc)) {
CERROR("failed to link key %08x to keyring %08x: %d\n",
key->serial,
key_tgcred(current)->session_keyring->serial, rc);
return rc;
}
CDEBUG(D_SEC, "key %p instantiated, ctx %p\n", key, key->payload.data);
return 0;
}
/*
* called with key semaphore write locked. it means we can operate
* on the context without fear of losing refcount.
*/
static
int gss_kt_update(struct key *key, const void *data, size_t datalen)
{
struct ptlrpc_cli_ctx *ctx = key->payload.data;
struct gss_cli_ctx *gctx;
rawobj_t tmpobj = RAWOBJ_EMPTY;
__u32 datalen32 = (__u32) datalen;
int rc;
if (data == NULL || datalen == 0) {
CWARN("invalid: data %p, len %lu\n", data, (long)datalen);
return -EINVAL;
}
/* if upcall finished negotiation too fast (mostly likely because
* of local error happened) and call kt_update(), the ctx
* might be still NULL. but the key will finally be associate
* with a context, or be revoked. if key status is fine, return
* -EAGAIN to allow userspace sleep a while and call again. */
if (ctx == NULL) {
CDEBUG(D_SEC, "update too soon: key %p(%x) flags %lx\n",
key, key->serial, key->flags);
rc = key_validate(key);
if (rc == 0)
return -EAGAIN;
else
return rc;
}
LASSERT(atomic_read(&ctx->cc_refcount) > 0);
LASSERT(ctx->cc_sec);
ctx_clear_timer_kr(ctx);
/* don't proceed if already refreshed */
if (cli_ctx_is_refreshed(ctx)) {
CWARN("ctx already done refresh\n");
return 0;
}
sptlrpc_cli_ctx_get(ctx);
gctx = ctx2gctx(ctx);
rc = buffer_extract_bytes(&data, &datalen32, &gctx->gc_win,
sizeof(gctx->gc_win));
if (rc) {
CERROR("failed extract seq_win\n");
goto out;
}
if (gctx->gc_win == 0) {
__u32 nego_rpc_err, nego_gss_err;
rc = buffer_extract_bytes(&data, &datalen32, &nego_rpc_err,
sizeof(nego_rpc_err));
if (rc) {
CERROR("failed to extrace rpc rc\n");
goto out;
}
rc = buffer_extract_bytes(&data, &datalen32, &nego_gss_err,
sizeof(nego_gss_err));
if (rc) {
CERROR("failed to extrace gss rc\n");
goto out;
}
CERROR("negotiation: rpc err %d, gss err %x\n",
nego_rpc_err, nego_gss_err);
rc = nego_rpc_err ? nego_rpc_err : -EACCES;
} else {
rc = rawobj_extract_local_alloc(&gctx->gc_handle,
(__u32 **) &data, &datalen32);
if (rc) {
CERROR("failed extract handle\n");
goto out;
}
rc = rawobj_extract_local(&tmpobj, (__u32 **) &data,&datalen32);
if (rc) {
CERROR("failed extract mech\n");
goto out;
}
rc = lgss_import_sec_context(&tmpobj,
sec2gsec(ctx->cc_sec)->gs_mech,
&gctx->gc_mechctx);
if (rc != GSS_S_COMPLETE)
CERROR("failed import context\n");
else
rc = 0;
}
out:
/* we don't care what current status of this ctx, even someone else
* is operating on the ctx at the same time. we just add up our own
* opinions here. */
if (rc == 0) {
gss_cli_ctx_uptodate(gctx);
} else {
/* this will also revoke the key. has to be done before
* wakeup waiters otherwise they can find the stale key */
kill_key_locked(key);
cli_ctx_expire(ctx);
if (rc != -ERESTART)
set_bit(PTLRPC_CTX_ERROR_BIT, &ctx->cc_flags);
}
/* let user space think it's a success */
sptlrpc_cli_ctx_put(ctx, 1);
return 0;
}
static
int gss_kt_match(const struct key *key, const void *desc)
{
return (strcmp(key->description, (const char *) desc) == 0);
}
static
void gss_kt_destroy(struct key *key)
{
LASSERT(key->payload.data == NULL);
CDEBUG(D_SEC, "destroy key %p\n", key);
}
static
void gss_kt_describe(const struct key *key, struct seq_file *s)
{
if (key->description == NULL)
seq_puts(s, "[null]");
else
seq_puts(s, key->description);
}
static struct key_type gss_key_type =
{
.name = "lgssc",
.def_datalen = 0,
.instantiate = gss_kt_instantiate,
.update = gss_kt_update,
.match = gss_kt_match,
.destroy = gss_kt_destroy,
.describe = gss_kt_describe,
};
/****************************************
* lustre gss keyring policy *
****************************************/
static struct ptlrpc_ctx_ops gss_keyring_ctxops = {
.match = gss_cli_ctx_match,
.refresh = gss_cli_ctx_refresh_kr,
.validate = gss_cli_ctx_validate_kr,
.die = gss_cli_ctx_die_kr,
.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,
};
static struct ptlrpc_sec_cops gss_sec_keyring_cops = {
.create_sec = gss_sec_create_kr,
.destroy_sec = gss_sec_destroy_kr,
.kill_sec = gss_sec_kill,
.lookup_ctx = gss_sec_lookup_ctx_kr,
.release_ctx = gss_sec_release_ctx_kr,
.flush_ctx_cache = gss_sec_flush_ctx_cache_kr,
.gc_ctx = gss_sec_gc_ctx_kr,
.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,
.enlarge_reqbuf = gss_enlarge_reqbuf,
.display = gss_sec_display_kr,
};
static struct ptlrpc_sec_sops gss_sec_keyring_sops = {
.accept = gss_svc_accept_kr,
.invalidate_ctx = gss_svc_invalidate_ctx,
.alloc_rs = gss_svc_alloc_rs,
.authorize = gss_svc_authorize,
.free_rs = gss_svc_free_rs,
.free_ctx = gss_svc_free_ctx,
.prep_bulk = gss_svc_prep_bulk,
.unwrap_bulk = gss_svc_unwrap_bulk,
.wrap_bulk = gss_svc_wrap_bulk,
.install_rctx = gss_svc_install_rctx_kr,
};
static struct ptlrpc_sec_policy gss_policy_keyring = {
.sp_owner = THIS_MODULE,
.sp_name = "gss.keyring",
.sp_policy = SPTLRPC_POLICY_GSS,
.sp_cops = &gss_sec_keyring_cops,
.sp_sops = &gss_sec_keyring_sops,
};
int __init gss_init_keyring(void)
{
int rc;
rc = register_key_type(&gss_key_type);
if (rc) {
CERROR("failed to register keyring type: %d\n", rc);
return rc;
}
rc = sptlrpc_register_policy(&gss_policy_keyring);
if (rc) {
unregister_key_type(&gss_key_type);
return rc;
}
return 0;
}
void __exit gss_exit_keyring(void)
{
unregister_key_type(&gss_key_type);
sptlrpc_unregister_policy(&gss_policy_keyring);
}
/*
* Modifications for Lustre
*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Author: Eric Mei <ericm@clusterfs.com>
*/
/*
* 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 <andros@umich.edu>
* Bruce Fields <bfields@umich.edu>
*/
/*
* 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
/*
* 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 ll_crypto_cipher *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 */
/*
* Modifications for Lustre
*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Copyright (c) 2011, 2012, Intel Corporation.
*
* Author: Eric Mei <ericm@clusterfs.com>
*/
/*
* linux/net/sunrpc/gss_krb5_mech.c
* linux/net/sunrpc/gss_krb5_crypto.c
* linux/net/sunrpc/gss_krb5_seal.c
* linux/net/sunrpc/gss_krb5_seqnum.c
* linux/net/sunrpc/gss_krb5_unseal.c
*
* Copyright (c) 2001 The Regents of the University of Michigan.
* All rights reserved.
*
* Andy Adamson <andros@umich.edu>
* J. Bruce Fields <bfields@umich.edu>
*
* 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.
*
*/
#define DEBUG_SUBSYSTEM S_SEC
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/crypto.h>
#include <linux/mutex.h>
#include <obd.h>
#include <obd_class.h>
#include <obd_support.h>
#include <lustre/lustre_idl.h>
#include <lustre_net.h>
#include <lustre_import.h>
#include <lustre_sec.h>
#include "gss_err.h"
#include "gss_internal.h"
#include "gss_api.h"
#include "gss_asn1.h"
#include "gss_krb5.h"
static spinlock_t krb5_seq_lock;
struct krb5_enctype {
char *ke_dispname;
char *ke_enc_name; /* linux tfm name */
char *ke_hash_name; /* linux tfm name */
int ke_enc_mode; /* linux tfm mode */
int ke_hash_size; /* checksum size */
int ke_conf_size; /* confounder size */
unsigned int ke_hash_hmac:1; /* is hmac? */
};
/*
* NOTE: for aes128-cts and aes256-cts, MIT implementation use CTS encryption.
* but currently we simply CBC with padding, because linux doesn't support CTS
* yet. this need to be fixed in the future.
*/
static struct krb5_enctype enctypes[] = {
[ENCTYPE_DES_CBC_RAW] = { /* des-cbc-md5 */
"des-cbc-md5",
"cbc(des)",
"md5",
0,
16,
8,
0,
},
[ENCTYPE_DES3_CBC_RAW] = { /* des3-hmac-sha1 */
"des3-hmac-sha1",
"cbc(des3_ede)",
"hmac(sha1)",
0,
20,
8,
1,
},
[ENCTYPE_AES128_CTS_HMAC_SHA1_96] = { /* aes128-cts */
"aes128-cts-hmac-sha1-96",
"cbc(aes)",
"hmac(sha1)",
0,
12,
16,
1,
},
[ENCTYPE_AES256_CTS_HMAC_SHA1_96] = { /* aes256-cts */
"aes256-cts-hmac-sha1-96",
"cbc(aes)",
"hmac(sha1)",
0,
12,
16,
1,
},
[ENCTYPE_ARCFOUR_HMAC] = { /* arcfour-hmac-md5 */
"arcfour-hmac-md5",
"ecb(arc4)",
"hmac(md5)",
0,
16,
8,
1,
},
};
#define MAX_ENCTYPES sizeof(enctypes)/sizeof(struct krb5_enctype)
static const char * enctype2str(__u32 enctype)
{
if (enctype < MAX_ENCTYPES && enctypes[enctype].ke_dispname)
return enctypes[enctype].ke_dispname;
return "unknown";
}
static
int keyblock_init(struct krb5_keyblock *kb, char *alg_name, int alg_mode)
{
kb->kb_tfm = crypto_alloc_blkcipher(alg_name, alg_mode, 0);
if (IS_ERR(kb->kb_tfm)) {
CERROR("failed to alloc tfm: %s, mode %d\n",
alg_name, alg_mode);
return -1;
}
if (crypto_blkcipher_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];
/* tfm arc4 is stateful, user should alloc-use-free by his own */
if (kctx->kc_enctype != ENCTYPE_ARCFOUR_HMAC &&
keyblock_init(&kctx->kc_keye, ke->ke_enc_name, ke->ke_enc_mode))
return -1;
/* tfm hmac is stateful, user should alloc-use-free by his own */
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_blkcipher(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;
__u32 len;
p = *ptr;
if (get_bytes(&p, end, &len, sizeof(len)))
return -1;
q = p + len;
if (q > end || q < p)
return -1;
OBD_ALLOC_LARGE(res->data, len);
if (!res->data)
return -1;
res->len = len;
memcpy(res->data, p, len);
*ptr = q;
return 0;
}
static
int get_keyblock(char **ptr, const char *end,
struct krb5_keyblock *kb, __u32 keysize)
{
char *buf;
OBD_ALLOC_LARGE(buf, keysize);
if (buf == NULL)
return -1;
if (get_bytes(ptr, end, buf, keysize)) {
OBD_FREE_LARGE(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, "successfully 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_rfc4121(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, "successfully 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_PTR(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_rfc4121(kctx, p, end);
}
if (rc == 0)
rc = krb5_init_keys(kctx);
if (rc) {
delete_context_kerberos(kctx);
OBD_FREE_PTR(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_PTR(knew);
if (!knew)
return GSS_S_FAILURE;
knew->kc_initiate = kctx->kc_initiate ? 0 : 1;
knew->kc_cfx = kctx->kc_cfx;
knew->kc_seed_init = kctx->kc_seed_init;
knew->kc_have_acceptor_subkey = kctx->kc_have_acceptor_subkey;
knew->kc_endtime = kctx->kc_endtime;
memcpy(knew->kc_seed, kctx->kc_seed, sizeof(kctx->kc_seed));
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, "successfully copied reverse context\n");
return GSS_S_COMPLETE;
out_err:
delete_context_kerberos(knew);
OBD_FREE_PTR(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_PTR(kctx);
}
static
void buf_to_sg(struct scatterlist *sg, void *ptr, int len)
{
sg_set_buf(sg, ptr, len);
}
static
__u32 krb5_encrypt(struct crypto_blkcipher *tfm,
int decrypt,
void * iv,
void * in,
void * out,
int length)
{
struct blkcipher_desc desc;
struct scatterlist sg;
__u8 local_iv[16] = {0};
__u32 ret = -EINVAL;
LASSERT(tfm);
desc.tfm = tfm;
desc.info = local_iv;
desc.flags= 0;
if (length % crypto_blkcipher_blocksize(tfm) != 0) {
CERROR("output length %d mismatch blocksize %d\n",
length, crypto_blkcipher_blocksize(tfm));
goto out;
}
if (crypto_blkcipher_ivsize(tfm) > 16) {
CERROR("iv size too large %d\n", crypto_blkcipher_ivsize(tfm));
goto out;
}
if (iv)
memcpy(local_iv, iv, crypto_blkcipher_ivsize(tfm));
memcpy(out, in, length);
buf_to_sg(&sg, out, length);
if (decrypt)
ret = crypto_blkcipher_decrypt_iv(&desc, &sg, &sg, length);
else
ret = crypto_blkcipher_encrypt_iv(&desc, &sg, &sg, length);
out:
return(ret);
}
static inline
int krb5_digest_hmac(struct crypto_hash *tfm,
rawobj_t *key,
struct krb5_header *khdr,
int msgcnt, rawobj_t *msgs,
int iovcnt, lnet_kiov_t *iovs,
rawobj_t *cksum)
{
struct hash_desc desc;
struct scatterlist sg[1];
int i;
crypto_hash_setkey(tfm, key->data, key->len);
desc.tfm = tfm;
desc.flags= 0;
crypto_hash_init(&desc);
for (i = 0; i < msgcnt; i++) {
if (msgs[i].len == 0)
continue;
buf_to_sg(sg, (char *) msgs[i].data, msgs[i].len);
crypto_hash_update(&desc, sg, msgs[i].len);
}
for (i = 0; i < iovcnt; i++) {
if (iovs[i].kiov_len == 0)
continue;
sg_set_page(&sg[0], iovs[i].kiov_page, iovs[i].kiov_len,
iovs[i].kiov_offset);
crypto_hash_update(&desc, sg, iovs[i].kiov_len);
}
if (khdr) {
buf_to_sg(sg, (char *) khdr, sizeof(*khdr));
crypto_hash_update(&desc, sg, sizeof(*khdr));
}
return crypto_hash_final(&desc, cksum->data);
}
static inline
int krb5_digest_norm(struct crypto_hash *tfm,
struct krb5_keyblock *kb,
struct krb5_header *khdr,
int msgcnt, rawobj_t *msgs,
int iovcnt, lnet_kiov_t *iovs,
rawobj_t *cksum)
{
struct hash_desc desc;
struct scatterlist sg[1];
int i;
LASSERT(kb->kb_tfm);
desc.tfm = tfm;
desc.flags= 0;
crypto_hash_init(&desc);
for (i = 0; i < msgcnt; i++) {
if (msgs[i].len == 0)
continue;
buf_to_sg(sg, (char *) msgs[i].data, msgs[i].len);
crypto_hash_update(&desc, sg, msgs[i].len);
}
for (i = 0; i < iovcnt; i++) {
if (iovs[i].kiov_len == 0)
continue;
sg_set_page(&sg[0], iovs[i].kiov_page, iovs[i].kiov_len,
iovs[i].kiov_offset);
crypto_hash_update(&desc, sg, iovs[i].kiov_len);
}
if (khdr) {
buf_to_sg(sg, (char *) khdr, sizeof(*khdr));
crypto_hash_update(&desc, sg, sizeof(*khdr));
}
crypto_hash_final(&desc, 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,
int iovcnt, lnet_kiov_t *iovs,
rawobj_t *cksum)
{
struct krb5_enctype *ke = &enctypes[enctype];
struct crypto_hash *tfm;
__u32 code = GSS_S_FAILURE;
int rc;
tfm = ll_crypto_alloc_hash(ke->ke_hash_name, 0, 0);
if (!tfm) {
CERROR("failed to alloc TFM: %s\n", ke->ke_hash_name);
return GSS_S_FAILURE;
}
cksum->len = crypto_hash_digestsize(tfm);
OBD_ALLOC_LARGE(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, iovcnt, iovs, cksum);
else
rc = krb5_digest_norm(tfm, kb,
khdr, msgcnt, msgs, iovcnt, iovs, cksum);
if (rc == 0)
code = GSS_S_COMPLETE;
out_tfm:
crypto_free_hash(tfm);
return code;
}
static void fill_krb5_header(struct krb5_ctx *kctx,
struct krb5_header *khdr,
int privacy)
{
unsigned char acceptor_flag;
acceptor_flag = kctx->kc_initiate ? 0 : FLAG_SENDER_IS_ACCEPTOR;
if (privacy) {
khdr->kh_tok_id = cpu_to_be16(KG_TOK_WRAP_MSG);
khdr->kh_flags = acceptor_flag | FLAG_WRAP_CONFIDENTIAL;
khdr->kh_ec = cpu_to_be16(0);
khdr->kh_rrc = cpu_to_be16(0);
} else {
khdr->kh_tok_id = cpu_to_be16(KG_TOK_MIC_MSG);
khdr->kh_flags = acceptor_flag;
khdr->kh_ec = cpu_to_be16(0xffff);
khdr->kh_rrc = cpu_to_be16(0xffff);
}
khdr->kh_filler = 0xff;
spin_lock(&krb5_seq_lock);
khdr->kh_seq = cpu_to_be64(kctx->kc_seq_send++);
spin_unlock(&krb5_seq_lock);
}
static __u32 verify_krb5_header(struct krb5_ctx *kctx,
struct krb5_header *khdr,
int privacy)
{
unsigned char acceptor_flag;
__u16 tok_id, ec_rrc;
acceptor_flag = kctx->kc_initiate ? FLAG_SENDER_IS_ACCEPTOR : 0;
if (privacy) {
tok_id = KG_TOK_WRAP_MSG;
ec_rrc = 0x0;
} else {
tok_id = KG_TOK_MIC_MSG;
ec_rrc = 0xffff;
}
/* sanity checks */
if (be16_to_cpu(khdr->kh_tok_id) != tok_id) {
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 (privacy && (khdr->kh_flags & FLAG_WRAP_CONFIDENTIAL) == 0) {
CERROR("missing confidential 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) != ec_rrc ||
be16_to_cpu(khdr->kh_rrc) != ec_rrc) {
CERROR("bad EC or RRC\n");
return GSS_S_DEFECTIVE_TOKEN;
}
return GSS_S_COMPLETE;
}
static
__u32 gss_get_mic_kerberos(struct gss_ctx *gctx,
int msgcnt,
rawobj_t *msgs,
int iovcnt,
lnet_kiov_t *iovs,
rawobj_t *token)
{
struct krb5_ctx *kctx = gctx->internal_ctx_id;
struct krb5_enctype *ke = &enctypes[kctx->kc_enctype];
struct krb5_header *khdr;
rawobj_t cksum = RAWOBJ_EMPTY;
/* fill krb5 header */
LASSERT(token->len >= sizeof(*khdr));
khdr = (struct krb5_header *) token->data;
fill_krb5_header(kctx, khdr, 0);
/* checksum */
if (krb5_make_checksum(kctx->kc_enctype, &kctx->kc_keyc,
khdr, msgcnt, msgs, iovcnt, iovs, &cksum))
return GSS_S_FAILURE;
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;
rawobj_free(&cksum);
return GSS_S_COMPLETE;
}
static
__u32 gss_verify_mic_kerberos(struct gss_ctx *gctx,
int msgcnt,
rawobj_t *msgs,
int iovcnt,
lnet_kiov_t *iovs,
rawobj_t *token)
{
struct krb5_ctx *kctx = gctx->internal_ctx_id;
struct krb5_enctype *ke = &enctypes[kctx->kc_enctype];
struct krb5_header *khdr;
rawobj_t cksum = RAWOBJ_EMPTY;
__u32 major;
if (token->len < sizeof(*khdr)) {
CERROR("short signature: %u\n", token->len);
return GSS_S_DEFECTIVE_TOKEN;
}
khdr = (struct krb5_header *) token->data;
major = verify_krb5_header(kctx, khdr, 0);
if (major != GSS_S_COMPLETE) {
CERROR("bad krb5 header\n");
return major;
}
if (token->len < sizeof(*khdr) + ke->ke_hash_size) {
CERROR("short signature: %u, require %d\n",
token->len, (int) sizeof(*khdr) + ke->ke_hash_size);
return GSS_S_FAILURE;
}
if (krb5_make_checksum(kctx->kc_enctype, &kctx->kc_keyc,
khdr, msgcnt, msgs, iovcnt, iovs, &cksum)) {
CERROR("failed to make checksum\n");
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");
rawobj_free(&cksum);
return GSS_S_BAD_SIG;
}
rawobj_free(&cksum);
return GSS_S_COMPLETE;
}
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_blkcipher *tfm,
int mode_ecb,
int inobj_cnt,
rawobj_t *inobjs,
rawobj_t *outobj,
int enc)
{
struct blkcipher_desc desc;
struct scatterlist src, dst;
__u8 local_iv[16] = {0}, *buf;
__u32 datalen = 0;
int i, rc;
buf = outobj->data;
desc.tfm = tfm;
desc.info = local_iv;
desc.flags = 0;
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 (mode_ecb) {
if (enc)
rc = crypto_blkcipher_encrypt(
&desc, &dst, &src, src.length);
else
rc = crypto_blkcipher_decrypt(
&desc, &dst, &src, src.length);
} else {
if (enc)
rc = crypto_blkcipher_encrypt_iv(
&desc, &dst, &src, src.length);
else
rc = crypto_blkcipher_decrypt_iv(
&desc, &dst, &src, src.length);
}
if (rc) {
CERROR("encrypt error %d\n", rc);
return rc;
}
datalen += inobjs[i].len;
buf += inobjs[i].len;
}
outobj->len = datalen;
return 0;
}
/*
* if adj_nob != 0, we adjust desc->bd_nob to the actual cipher text size.
*/
static
int krb5_encrypt_bulk(struct crypto_blkcipher *tfm,
struct krb5_header *khdr,
char *confounder,
struct ptlrpc_bulk_desc *desc,
rawobj_t *cipher,
int adj_nob)
{
struct blkcipher_desc ciph_desc;
__u8 local_iv[16] = {0};
struct scatterlist src, dst;
int blocksize, i, rc, nob = 0;
LASSERT(desc->bd_iov_count);
LASSERT(desc->bd_enc_iov);
blocksize = crypto_blkcipher_blocksize(tfm);
LASSERT(blocksize > 1);
LASSERT(cipher->len == blocksize + sizeof(*khdr));
ciph_desc.tfm = tfm;
ciph_desc.info = local_iv;
ciph_desc.flags = 0;
/* encrypt confounder */
buf_to_sg(&src, confounder, blocksize);
buf_to_sg(&dst, cipher->data, blocksize);
rc = crypto_blkcipher_encrypt_iv(&ciph_desc, &dst, &src, blocksize);
if (rc) {
CERROR("error to encrypt confounder: %d\n", rc);
return rc;
}
/* encrypt clear pages */
for (i = 0; i < desc->bd_iov_count; i++) {
sg_set_page(&src, desc->bd_iov[i].kiov_page,
(desc->bd_iov[i].kiov_len + blocksize - 1) &
(~(blocksize - 1)),
desc->bd_iov[i].kiov_offset);
if (adj_nob)
nob += src.length;
sg_set_page(&dst, desc->bd_enc_iov[i].kiov_page, src.length,
src.offset);
desc->bd_enc_iov[i].kiov_offset = dst.offset;
desc->bd_enc_iov[i].kiov_len = dst.length;
rc = crypto_blkcipher_encrypt_iv(&ciph_desc, &dst, &src,
src.length);
if (rc) {
CERROR("error to encrypt page: %d\n", rc);
return rc;
}
}
/* encrypt krb5 header */
buf_to_sg(&src, khdr, sizeof(*khdr));
buf_to_sg(&dst, cipher->data + blocksize, sizeof(*khdr));
rc = crypto_blkcipher_encrypt_iv(&ciph_desc,
&dst, &src, sizeof(*khdr));
if (rc) {
CERROR("error to encrypt krb5 header: %d\n", rc);
return rc;
}
if (adj_nob)
desc->bd_nob = nob;
return 0;
}
/*
* desc->bd_nob_transferred is the size of cipher text received.
* desc->bd_nob is the target size of plain text supposed to be.
*
* if adj_nob != 0, we adjust each page's kiov_len to the actual
* plain text size.
* - for client read: we don't know data size for each page, so
* bd_iov[]->kiov_len is set to PAGE_SIZE, but actual data received might
* be smaller, so we need to adjust it according to bd_enc_iov[]->kiov_len.
* this means we DO NOT support the situation that server send an odd size
* data in a page which is not the last one.
* - for server write: we knows exactly data size for each page being expected,
* thus kiov_len is accurate already, so we should not adjust it at all.
* and bd_enc_iov[]->kiov_len should be round_up(bd_iov[]->kiov_len) which
* should have been done by prep_bulk().
*/
static
int krb5_decrypt_bulk(struct crypto_blkcipher *tfm,
struct krb5_header *khdr,
struct ptlrpc_bulk_desc *desc,
rawobj_t *cipher,
rawobj_t *plain,
int adj_nob)
{
struct blkcipher_desc ciph_desc;
__u8 local_iv[16] = {0};
struct scatterlist src, dst;
int ct_nob = 0, pt_nob = 0;
int blocksize, i, rc;
LASSERT(desc->bd_iov_count);
LASSERT(desc->bd_enc_iov);
LASSERT(desc->bd_nob_transferred);
blocksize = crypto_blkcipher_blocksize(tfm);
LASSERT(blocksize > 1);
LASSERT(cipher->len == blocksize + sizeof(*khdr));
ciph_desc.tfm = tfm;
ciph_desc.info = local_iv;
ciph_desc.flags = 0;
if (desc->bd_nob_transferred % blocksize) {
CERROR("odd transferred nob: %d\n", desc->bd_nob_transferred);
return -EPROTO;
}
/* decrypt head (confounder) */
buf_to_sg(&src, cipher->data, blocksize);
buf_to_sg(&dst, plain->data, blocksize);
rc = crypto_blkcipher_decrypt_iv(&ciph_desc, &dst, &src, blocksize);
if (rc) {
CERROR("error to decrypt confounder: %d\n", rc);
return rc;
}
for (i = 0; i < desc->bd_iov_count && ct_nob < desc->bd_nob_transferred;
i++) {
if (desc->bd_enc_iov[i].kiov_offset % blocksize != 0 ||
desc->bd_enc_iov[i].kiov_len % blocksize != 0) {
CERROR("page %d: odd offset %u len %u, blocksize %d\n",
i, desc->bd_enc_iov[i].kiov_offset,
desc->bd_enc_iov[i].kiov_len, blocksize);
return -EFAULT;
}
if (adj_nob) {
if (ct_nob + desc->bd_enc_iov[i].kiov_len >
desc->bd_nob_transferred)
desc->bd_enc_iov[i].kiov_len =
desc->bd_nob_transferred - ct_nob;
desc->bd_iov[i].kiov_len = desc->bd_enc_iov[i].kiov_len;
if (pt_nob + desc->bd_enc_iov[i].kiov_len >desc->bd_nob)
desc->bd_iov[i].kiov_len = desc->bd_nob -pt_nob;
} else {
/* this should be guaranteed by LNET */
LASSERT(ct_nob + desc->bd_enc_iov[i].kiov_len <=
desc->bd_nob_transferred);
LASSERT(desc->bd_iov[i].kiov_len <=
desc->bd_enc_iov[i].kiov_len);
}
if (desc->bd_enc_iov[i].kiov_len == 0)
continue;
sg_set_page(&src, desc->bd_enc_iov[i].kiov_page,
desc->bd_enc_iov[i].kiov_len,
desc->bd_enc_iov[i].kiov_offset);
dst = src;
if (desc->bd_iov[i].kiov_len % blocksize == 0)
sg_assign_page(&dst, desc->bd_iov[i].kiov_page);
rc = crypto_blkcipher_decrypt_iv(&ciph_desc, &dst, &src,
src.length);
if (rc) {
CERROR("error to decrypt page: %d\n", rc);
return rc;
}
if (desc->bd_iov[i].kiov_len % blocksize != 0) {
memcpy(page_address(desc->bd_iov[i].kiov_page) +
desc->bd_iov[i].kiov_offset,
page_address(desc->bd_enc_iov[i].kiov_page) +
desc->bd_iov[i].kiov_offset,
desc->bd_iov[i].kiov_len);
}
ct_nob += desc->bd_enc_iov[i].kiov_len;
pt_nob += desc->bd_iov[i].kiov_len;
}
if (unlikely(ct_nob != desc->bd_nob_transferred)) {
CERROR("%d cipher text transferred but only %d decrypted\n",
desc->bd_nob_transferred, ct_nob);
return -EFAULT;
}
if (unlikely(!adj_nob && pt_nob != desc->bd_nob)) {
CERROR("%d plain text expected but only %d received\n",
desc->bd_nob, pt_nob);
return -EFAULT;
}
/* if needed, clear up the rest unused iovs */
if (adj_nob)
while (i < desc->bd_iov_count)
desc->bd_iov[i++].kiov_len = 0;
/* decrypt tail (krb5 header) */
buf_to_sg(&src, cipher->data + blocksize, sizeof(*khdr));
buf_to_sg(&dst, cipher->data + blocksize, sizeof(*khdr));
rc = crypto_blkcipher_decrypt_iv(&ciph_desc,
&dst, &src, sizeof(*khdr));
if (rc) {
CERROR("error to decrypt tail: %d\n", rc);
return rc;
}
if (memcmp(cipher->data + blocksize, khdr, sizeof(*khdr))) {
CERROR("krb5 header doesn't match\n");
return -EACCES;
}
return 0;
}
static
__u32 gss_wrap_kerberos(struct gss_ctx *gctx,
rawobj_t *gsshdr,
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;
int blocksize;
rawobj_t cksum = RAWOBJ_EMPTY;
rawobj_t data_desc[3], cipher;
__u8 conf[GSS_MAX_CIPHER_BLOCK];
int rc = 0;
LASSERT(ke);
LASSERT(ke->ke_conf_size <= GSS_MAX_CIPHER_BLOCK);
LASSERT(kctx->kc_keye.kb_tfm == NULL ||
ke->ke_conf_size >=
crypto_blkcipher_blocksize(kctx->kc_keye.kb_tfm));
/*
* final token format:
* ---------------------------------------------------
* | krb5 header | cipher text | checksum (16 bytes) |
* ---------------------------------------------------
*/
/* fill krb5 header */
LASSERT(token->len >= sizeof(*khdr));
khdr = (struct krb5_header *) token->data;
fill_krb5_header(kctx, khdr, 1);
/* generate confounder */
cfs_get_random_bytes(conf, ke->ke_conf_size);
/* get encryption blocksize. note kc_keye might not associated with
* a tfm, currently only for arcfour-hmac */
if (kctx->kc_enctype == ENCTYPE_ARCFOUR_HMAC) {
LASSERT(kctx->kc_keye.kb_tfm == NULL);
blocksize = 1;
} else {
LASSERT(kctx->kc_keye.kb_tfm);
blocksize = crypto_blkcipher_blocksize(kctx->kc_keye.kb_tfm);
}
LASSERT(blocksize <= ke->ke_conf_size);
/* padding the message */
if (add_padding(msg, msg_buflen, blocksize))
return GSS_S_FAILURE;
/*
* clear text layout for checksum:
* ------------------------------------------------------
* | confounder | gss header | clear msgs | krb5 header |
* ------------------------------------------------------
*/
data_desc[0].data = conf;
data_desc[0].len = ke->ke_conf_size;
data_desc[1].data = gsshdr->data;
data_desc[1].len = gsshdr->len;
data_desc[2].data = msg->data;
data_desc[2].len = msg->len;
/* compute checksum */
if (krb5_make_checksum(kctx->kc_enctype, &kctx->kc_keyi,
khdr, 3, data_desc, 0, NULL, &cksum))
return GSS_S_FAILURE;
LASSERT(cksum.len >= ke->ke_hash_size);
/*
* clear text layout for encryption:
* -----------------------------------------
* | confounder | clear msgs | krb5 header |
* -----------------------------------------
*/
data_desc[0].data = conf;
data_desc[0].len = ke->ke_conf_size;
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 text will be directly inplace */
cipher.data = (__u8 *) (khdr + 1);
cipher.len = token->len - sizeof(*khdr);
LASSERT(cipher.len >= ke->ke_conf_size + msg->len + sizeof(*khdr));
if (kctx->kc_enctype == ENCTYPE_ARCFOUR_HMAC) {
rawobj_t arc4_keye;
struct crypto_blkcipher *arc4_tfm;
if (krb5_make_checksum(ENCTYPE_ARCFOUR_HMAC, &kctx->kc_keyi,
NULL, 1, &cksum, 0, NULL, &arc4_keye)) {
CERROR("failed to obtain arc4 enc key\n");
GOTO(arc4_out, rc = -EACCES);
}
arc4_tfm = crypto_alloc_blkcipher("ecb(arc4)", 0, 0);
if (IS_ERR(arc4_tfm)) {
CERROR("failed to alloc tfm arc4 in ECB mode\n");
GOTO(arc4_out_key, rc = -EACCES);
}
if (crypto_blkcipher_setkey(arc4_tfm, arc4_keye.data,
arc4_keye.len)) {
CERROR("failed to set arc4 key, len %d\n",
arc4_keye.len);
GOTO(arc4_out_tfm, rc = -EACCES);
}
rc = krb5_encrypt_rawobjs(arc4_tfm, 1,
3, data_desc, &cipher, 1);
arc4_out_tfm:
crypto_free_blkcipher(arc4_tfm);
arc4_out_key:
rawobj_free(&arc4_keye);
arc4_out:
do {} while (0); /* just to avoid compile warning */
} else {
rc = krb5_encrypt_rawobjs(kctx->kc_keye.kb_tfm, 0,
3, data_desc, &cipher, 1);
}
if (rc != 0) {
rawobj_free(&cksum);
return GSS_S_FAILURE;
}
/* fill in checksum */
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);
/* final token length */
token->len = sizeof(*khdr) + cipher.len + ke->ke_hash_size;
return GSS_S_COMPLETE;
}
static
__u32 gss_prep_bulk_kerberos(struct gss_ctx *gctx,
struct ptlrpc_bulk_desc *desc)
{
struct krb5_ctx *kctx = gctx->internal_ctx_id;
int blocksize, i;
LASSERT(desc->bd_iov_count);
LASSERT(desc->bd_enc_iov);
LASSERT(kctx->kc_keye.kb_tfm);
blocksize = crypto_blkcipher_blocksize(kctx->kc_keye.kb_tfm);
for (i = 0; i < desc->bd_iov_count; i++) {
LASSERT(desc->bd_enc_iov[i].kiov_page);
/*
* offset should always start at page boundary of either
* client or server side.
*/
if (desc->bd_iov[i].kiov_offset & blocksize) {
CERROR("odd offset %d in page %d\n",
desc->bd_iov[i].kiov_offset, i);
return GSS_S_FAILURE;
}
desc->bd_enc_iov[i].kiov_offset = desc->bd_iov[i].kiov_offset;
desc->bd_enc_iov[i].kiov_len = (desc->bd_iov[i].kiov_len +
blocksize - 1) & (~(blocksize - 1));
}
return GSS_S_COMPLETE;
}
static
__u32 gss_wrap_bulk_kerberos(struct gss_ctx *gctx,
struct ptlrpc_bulk_desc *desc,
rawobj_t *token, int adj_nob)
{
struct krb5_ctx *kctx = gctx->internal_ctx_id;
struct krb5_enctype *ke = &enctypes[kctx->kc_enctype];
struct krb5_header *khdr;
int blocksize;
rawobj_t cksum = RAWOBJ_EMPTY;
rawobj_t data_desc[1], cipher;
__u8 conf[GSS_MAX_CIPHER_BLOCK];
int rc = 0;
LASSERT(ke);
LASSERT(ke->ke_conf_size <= GSS_MAX_CIPHER_BLOCK);
/*
* final token format:
* --------------------------------------------------
* | krb5 header | head/tail cipher text | checksum |
* --------------------------------------------------
*/
/* fill krb5 header */
LASSERT(token->len >= sizeof(*khdr));
khdr = (struct krb5_header *) token->data;
fill_krb5_header(kctx, khdr, 1);
/* generate confounder */
cfs_get_random_bytes(conf, ke->ke_conf_size);
/* get encryption blocksize. note kc_keye might not associated with
* a tfm, currently only for arcfour-hmac */
if (kctx->kc_enctype == ENCTYPE_ARCFOUR_HMAC) {
LASSERT(kctx->kc_keye.kb_tfm == NULL);
blocksize = 1;
} else {
LASSERT(kctx->kc_keye.kb_tfm);
blocksize = crypto_blkcipher_blocksize(kctx->kc_keye.kb_tfm);
}
/*
* we assume the size of krb5_header (16 bytes) must be n * blocksize.
* the bulk token size would be exactly (sizeof(krb5_header) +
* blocksize + sizeof(krb5_header) + hashsize)
*/
LASSERT(blocksize <= ke->ke_conf_size);
LASSERT(sizeof(*khdr) >= blocksize && sizeof(*khdr) % blocksize == 0);
LASSERT(token->len >= sizeof(*khdr) + blocksize + sizeof(*khdr) + 16);
/*
* clear text layout for checksum:
* ------------------------------------------
* | confounder | clear pages | krb5 header |
* ------------------------------------------
*/
data_desc[0].data = conf;
data_desc[0].len = ke->ke_conf_size;
/* compute checksum */
if (krb5_make_checksum(kctx->kc_enctype, &kctx->kc_keyi,
khdr, 1, data_desc,
desc->bd_iov_count, desc->bd_iov,
&cksum))
return GSS_S_FAILURE;
LASSERT(cksum.len >= ke->ke_hash_size);
/*
* clear text layout for encryption:
* ------------------------------------------
* | confounder | clear pages | krb5 header |
* ------------------------------------------
* | | |
* ---------- (cipher pages) |
* result token: | |
* -------------------------------------------
* | krb5 header | cipher text | cipher text |
* -------------------------------------------
*/
data_desc[0].data = conf;
data_desc[0].len = ke->ke_conf_size;
cipher.data = (__u8 *) (khdr + 1);
cipher.len = blocksize + sizeof(*khdr);
if (kctx->kc_enctype == ENCTYPE_ARCFOUR_HMAC) {
LBUG();
rc = 0;
} else {
rc = krb5_encrypt_bulk(kctx->kc_keye.kb_tfm, khdr,
conf, desc, &cipher, adj_nob);
}
if (rc != 0) {
rawobj_free(&cksum);
return GSS_S_FAILURE;
}
/* fill in checksum */
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);
/* final token length */
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 *gsshdr,
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 *tmpbuf;
int blocksize, bodysize;
rawobj_t cksum = RAWOBJ_EMPTY;
rawobj_t cipher_in, plain_out;
rawobj_t hash_objs[3];
int rc = 0;
__u32 major;
LASSERT(ke);
if (token->len < sizeof(*khdr)) {
CERROR("short signature: %u\n", token->len);
return GSS_S_DEFECTIVE_TOKEN;
}
khdr = (struct krb5_header *) token->data;
major = verify_krb5_header(kctx, khdr, 1);
if (major != GSS_S_COMPLETE) {
CERROR("bad krb5 header\n");
return major;
}
/* block size */
if (kctx->kc_enctype == ENCTYPE_ARCFOUR_HMAC) {
LASSERT(kctx->kc_keye.kb_tfm == NULL);
blocksize = 1;
} else {
LASSERT(kctx->kc_keye.kb_tfm);
blocksize = crypto_blkcipher_blocksize(kctx->kc_keye.kb_tfm);
}
/* expected token layout:
* ----------------------------------------
* | 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 <= ke->ke_conf_size + sizeof(*khdr)) {
CERROR("incomplete token: bodysize %d\n", bodysize);
return GSS_S_DEFECTIVE_TOKEN;
}
if (msg->len < bodysize - ke->ke_conf_size - sizeof(*khdr)) {
CERROR("buffer too small: %u, require %d\n",
msg->len, bodysize - ke->ke_conf_size);
return GSS_S_FAILURE;
}
/* decrypting */
OBD_ALLOC_LARGE(tmpbuf, bodysize);
if (!tmpbuf)
return GSS_S_FAILURE;
major = GSS_S_FAILURE;
cipher_in.data = (__u8 *) (khdr + 1);
cipher_in.len = bodysize;
plain_out.data = tmpbuf;
plain_out.len = bodysize;
if (kctx->kc_enctype == ENCTYPE_ARCFOUR_HMAC) {
rawobj_t arc4_keye;
struct crypto_blkcipher *arc4_tfm;
cksum.data = token->data + token->len - ke->ke_hash_size;
cksum.len = ke->ke_hash_size;
if (krb5_make_checksum(ENCTYPE_ARCFOUR_HMAC, &kctx->kc_keyi,
NULL, 1, &cksum, 0, NULL, &arc4_keye)) {
CERROR("failed to obtain arc4 enc key\n");
GOTO(arc4_out, rc = -EACCES);
}
arc4_tfm = crypto_alloc_blkcipher("ecb(arc4)", 0, 0);
if (IS_ERR(arc4_tfm)) {
CERROR("failed to alloc tfm arc4 in ECB mode\n");
GOTO(arc4_out_key, rc = -EACCES);
}
if (crypto_blkcipher_setkey(arc4_tfm,
arc4_keye.data, arc4_keye.len)) {
CERROR("failed to set arc4 key, len %d\n",
arc4_keye.len);
GOTO(arc4_out_tfm, rc = -EACCES);
}
rc = krb5_encrypt_rawobjs(arc4_tfm, 1,
1, &cipher_in, &plain_out, 0);
arc4_out_tfm:
crypto_free_blkcipher(arc4_tfm);
arc4_out_key:
rawobj_free(&arc4_keye);
arc4_out:
cksum = RAWOBJ_EMPTY;
} else {
rc = krb5_encrypt_rawobjs(kctx->kc_keye.kb_tfm, 0,
1, &cipher_in, &plain_out, 0);
}
if (rc != 0) {
CERROR("error decrypt\n");
goto out_free;
}
LASSERT(plain_out.len == bodysize);
/* expected clear text layout:
* -----------------------------------------
* | confounder | clear msgs | krb5 header |
* -----------------------------------------
*/
/* verify krb5 header in token is not modified */
if (memcmp(khdr, plain_out.data + plain_out.len - sizeof(*khdr),
sizeof(*khdr))) {
CERROR("decrypted krb5 header mismatch\n");
goto out_free;
}
/* verify checksum, compose clear text as layout:
* ------------------------------------------------------
* | confounder | gss header | clear msgs | krb5 header |
* ------------------------------------------------------
*/
hash_objs[0].len = ke->ke_conf_size;
hash_objs[0].data = plain_out.data;
hash_objs[1].len = gsshdr->len;
hash_objs[1].data = gsshdr->data;
hash_objs[2].len = plain_out.len - ke->ke_conf_size - sizeof(*khdr);
hash_objs[2].data = plain_out.data + ke->ke_conf_size;
if (krb5_make_checksum(kctx->kc_enctype, &kctx->kc_keyi,
khdr, 3, hash_objs, 0, NULL, &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("checksum mismatch\n");
goto out_free;
}
msg->len = bodysize - ke->ke_conf_size - sizeof(*khdr);
memcpy(msg->data, tmpbuf + ke->ke_conf_size, msg->len);
major = GSS_S_COMPLETE;
out_free:
OBD_FREE_LARGE(tmpbuf, bodysize);
rawobj_free(&cksum);
return major;
}
static
__u32 gss_unwrap_bulk_kerberos(struct gss_ctx *gctx,
struct ptlrpc_bulk_desc *desc,
rawobj_t *token, int adj_nob)
{
struct krb5_ctx *kctx = gctx->internal_ctx_id;
struct krb5_enctype *ke = &enctypes[kctx->kc_enctype];
struct krb5_header *khdr;
int blocksize;
rawobj_t cksum = RAWOBJ_EMPTY;
rawobj_t cipher, plain;
rawobj_t data_desc[1];
int rc;
__u32 major;
LASSERT(ke);
if (token->len < sizeof(*khdr)) {
CERROR("short signature: %u\n", token->len);
return GSS_S_DEFECTIVE_TOKEN;
}
khdr = (struct krb5_header *) token->data;
major = verify_krb5_header(kctx, khdr, 1);
if (major != GSS_S_COMPLETE) {
CERROR("bad krb5 header\n");
return major;
}
/* block size */
if (kctx->kc_enctype == ENCTYPE_ARCFOUR_HMAC) {
LASSERT(kctx->kc_keye.kb_tfm == NULL);
blocksize = 1;
LBUG();
} else {
LASSERT(kctx->kc_keye.kb_tfm);
blocksize = crypto_blkcipher_blocksize(kctx->kc_keye.kb_tfm);
}
LASSERT(sizeof(*khdr) >= blocksize && sizeof(*khdr) % blocksize == 0);
/*
* token format is expected as:
* -----------------------------------------------
* | krb5 header | head/tail cipher text | cksum |
* -----------------------------------------------
*/
if (token->len < sizeof(*khdr) + blocksize + sizeof(*khdr) +
ke->ke_hash_size) {
CERROR("short token size: %u\n", token->len);
return GSS_S_DEFECTIVE_TOKEN;
}
cipher.data = (__u8 *) (khdr + 1);
cipher.len = blocksize + sizeof(*khdr);
plain.data = cipher.data;
plain.len = cipher.len;
rc = krb5_decrypt_bulk(kctx->kc_keye.kb_tfm, khdr,
desc, &cipher, &plain, adj_nob);
if (rc)
return GSS_S_DEFECTIVE_TOKEN;
/*
* verify checksum, compose clear text as layout:
* ------------------------------------------
* | confounder | clear pages | krb5 header |
* ------------------------------------------
*/
data_desc[0].data = plain.data;
data_desc[0].len = blocksize;
if (krb5_make_checksum(kctx->kc_enctype, &kctx->kc_keyi,
khdr, 1, data_desc,
desc->bd_iov_count, desc->bd_iov,
&cksum))
return GSS_S_FAILURE;
LASSERT(cksum.len >= ke->ke_hash_size);
if (memcmp(plain.data + blocksize + sizeof(*khdr),
cksum.data + cksum.len - ke->ke_hash_size,
ke->ke_hash_size)) {
CERROR("checksum mismatch\n");
rawobj_free(&cksum);
return GSS_S_BAD_SIG;
}
rawobj_free(&cksum);
return GSS_S_COMPLETE;
}
int gss_display_kerberos(struct gss_ctx *ctx,
char *buf,
int bufsize)
{
struct krb5_ctx *kctx = ctx->internal_ctx_id;
int written;
written = snprintf(buf, bufsize, "krb5 (%s)",
enctype2str(kctx->kc_enctype));
return written;
}
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_prep_bulk = gss_prep_bulk_kerberos,
.gss_wrap_bulk = gss_wrap_bulk_kerberos,
.gss_unwrap_bulk = gss_unwrap_bulk_kerberos,
.gss_delete_sec_context = gss_delete_sec_context_kerberos,
.gss_display = gss_display_kerberos,
};
static struct subflavor_desc gss_kerberos_sfs[] = {
{
.sf_subflavor = SPTLRPC_SUBFLVR_KRB5N,
.sf_qop = 0,
.sf_service = SPTLRPC_SVC_NULL,
.sf_name = "krb5n"
},
{
.sf_subflavor = SPTLRPC_SUBFLVR_KRB5A,
.sf_qop = 0,
.sf_service = SPTLRPC_SVC_AUTH,
.sf_name = "krb5a"
},
{
.sf_subflavor = SPTLRPC_SUBFLVR_KRB5I,
.sf_qop = 0,
.sf_service = SPTLRPC_SVC_INTG,
.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 = 4,
.gm_sfs = gss_kerberos_sfs,
};
int __init init_kerberos_module(void)
{
int status;
spin_lock_init(&krb5_seq_lock);
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);
}
/*
* Modifications for Lustre
*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Copyright (c) 2012, Intel Corporation.
*
* Author: Eric Mei <ericm@clusterfs.com>
*/
/*
* linux/net/sunrpc/gss_mech_switch.c
*
* Copyright (c) 2001 The Regents of the University of Michigan.
* All rights reserved.
*
* J. Bruce Fields <bfields@umich.edu>
*
* 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.
*
*/
#define DEBUG_SUBSYSTEM S_SEC
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <obd.h>
#include <obd_class.h>
#include <obd_support.h>
#include <lustre/lustre_idl.h>
#include <lustre_net.h>
#include <lustre_import.h>
#include <lustre_sec.h>
#include "gss_err.h"
#include "gss_internal.h"
#include "gss_api.h"
static LIST_HEAD(registered_mechs);
static DEFINE_SPINLOCK(registered_mechs_lock);
int lgss_mech_register(struct gss_api_mech *gm)
{
spin_lock(&registered_mechs_lock);
list_add(&gm->gm_list, &registered_mechs);
spin_unlock(&registered_mechs_lock);
CWARN("Register %s mechanism\n", gm->gm_name);
return 0;
}
void lgss_mech_unregister(struct gss_api_mech *gm)
{
spin_lock(&registered_mechs_lock);
list_del(&gm->gm_list);
spin_unlock(&registered_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(&registered_mechs_lock);
list_for_each_entry(pos, &registered_mechs, gm_list) {
if (0 == strcmp(name, pos->gm_name)) {
if (!try_module_get(pos->gm_owner))
continue;
gm = pos;
break;
}
}
spin_unlock(&registered_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(&registered_mechs_lock);
list_for_each_entry(pos, &registered_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(&registered_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_PTR(*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_PTR(*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_PTR(*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,
int iovcnt,
lnet_kiov_t *iovs,
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,
iovcnt,
iovs,
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,
int iovcnt,
lnet_kiov_t *iovs,
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,
iovcnt,
iovs,
mic_token);
}
__u32 lgss_wrap(struct gss_ctx *context_handle,
rawobj_t *gsshdr,
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, gsshdr, msg, msg_buflen, out_token);
}
__u32 lgss_unwrap(struct gss_ctx *context_handle,
rawobj_t *gsshdr,
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, gsshdr, token, out_msg);
}
__u32 lgss_prep_bulk(struct gss_ctx *context_handle,
struct ptlrpc_bulk_desc *desc)
{
LASSERT(context_handle);
LASSERT(context_handle->mech_type);
LASSERT(context_handle->mech_type->gm_ops);
LASSERT(context_handle->mech_type->gm_ops->gss_prep_bulk);
return context_handle->mech_type->gm_ops
->gss_prep_bulk(context_handle, desc);
}
__u32 lgss_wrap_bulk(struct gss_ctx *context_handle,
struct ptlrpc_bulk_desc *desc,
rawobj_t *token,
int adj_nob)
{
LASSERT(context_handle);
LASSERT(context_handle->mech_type);
LASSERT(context_handle->mech_type->gm_ops);
LASSERT(context_handle->mech_type->gm_ops->gss_wrap_bulk);
return context_handle->mech_type->gm_ops
->gss_wrap_bulk(context_handle, desc, token, adj_nob);
}
__u32 lgss_unwrap_bulk(struct gss_ctx *context_handle,
struct ptlrpc_bulk_desc *desc,
rawobj_t *token,
int adj_nob)
{
LASSERT(context_handle);
LASSERT(context_handle->mech_type);
LASSERT(context_handle->mech_type->gm_ops);
LASSERT(context_handle->mech_type->gm_ops->gss_unwrap_bulk);
return context_handle->mech_type->gm_ops
->gss_unwrap_bulk(context_handle, desc, token, adj_nob);
}
/* 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_PTR(*context_handle);
*context_handle=NULL;
return GSS_S_COMPLETE;
}
int lgss_display(struct gss_ctx *ctx,
char *buf,
int bufsize)
{
LASSERT(ctx);
LASSERT(ctx->mech_type);
LASSERT(ctx->mech_type->gm_ops);
LASSERT(ctx->mech_type->gm_ops->gss_display);
return ctx->mech_type->gm_ops->gss_display(ctx, buf, bufsize);
}
/*
* Modifications for Lustre
*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Copyright (c) 2012, Intel Corporation.
*
* Author: Eric Mei <ericm@clusterfs.com>
*/
/*
* 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 <dugsong@monkey.org>
* Andy Adamson <andros@umich.edu>
*
* 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.
*
*/
#define DEBUG_SUBSYSTEM S_SEC
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/dcache.h>
#include <linux/fs.h>
#include <linux/mutex.h>
#include <linux/crypto.h>
#include <asm/atomic.h>
struct rpc_clnt; /* for rpc_pipefs */
#include <linux/sunrpc/rpc_pipe_fs.h>
#include <obd.h>
#include <obd_class.h>
#include <obd_support.h>
#include <lustre/lustre_idl.h>
#include <lustre_sec.h>
#include <lustre_net.h>
#include <lustre_import.h>
#include "gss_err.h"
#include "gss_internal.h"
#include "gss_api.h"
static struct ptlrpc_sec_policy gss_policy_pipefs;
static struct ptlrpc_ctx_ops gss_pipefs_ctxops;
static int gss_cli_ctx_refresh_pf(struct ptlrpc_cli_ctx *ctx);
static int gss_sec_pipe_upcall_init(struct gss_sec *gsec)
{
return 0;
}
static void gss_sec_pipe_upcall_fini(struct gss_sec *gsec)
{
}
/****************************************
* internal context helpers *
****************************************/
static
struct ptlrpc_cli_ctx *ctx_create_pf(struct ptlrpc_sec *sec,
struct vfs_cred *vcred)
{
struct gss_cli_ctx *gctx;
int rc;
OBD_ALLOC_PTR(gctx);
if (gctx == NULL)
return NULL;
rc = gss_cli_ctx_init_common(sec, &gctx->gc_base,
&gss_pipefs_ctxops, vcred);
if (rc) {
OBD_FREE_PTR(gctx);
return NULL;
}
return &gctx->gc_base;
}
static
void ctx_destroy_pf(struct ptlrpc_sec *sec, struct ptlrpc_cli_ctx *ctx)
{
struct gss_cli_ctx *gctx = ctx2gctx(ctx);
if (gss_cli_ctx_fini_common(sec, ctx))
return;
OBD_FREE_PTR(gctx);
atomic_dec(&sec->ps_nctx);
sptlrpc_sec_put(sec);
}
static
void ctx_enhash_pf(struct ptlrpc_cli_ctx *ctx, struct hlist_head *hash)
{
set_bit(PTLRPC_CTX_CACHED_BIT, &ctx->cc_flags);
atomic_inc(&ctx->cc_refcount);
hlist_add_head(&ctx->cc_cache, hash);
}
/*
* caller must hold spinlock
*/
static
void ctx_unhash_pf(struct ptlrpc_cli_ctx *ctx, struct hlist_head *freelist)
{
assert_spin_locked(&ctx->cc_sec->ps_lock);
LASSERT(atomic_read(&ctx->cc_refcount) > 0);
LASSERT(test_bit(PTLRPC_CTX_CACHED_BIT, &ctx->cc_flags));
LASSERT(!hlist_unhashed(&ctx->cc_cache));
clear_bit(PTLRPC_CTX_CACHED_BIT, &ctx->cc_flags);
if (atomic_dec_and_test(&ctx->cc_refcount)) {
__hlist_del(&ctx->cc_cache);
hlist_add_head(&ctx->cc_cache, freelist);
} else {
hlist_del_init(&ctx->cc_cache);
}
}
/*
* return 1 if the context is dead.
*/
static
int ctx_check_death_pf(struct ptlrpc_cli_ctx *ctx,
struct hlist_head *freelist)
{
if (cli_ctx_check_death(ctx)) {
if (freelist)
ctx_unhash_pf(ctx, freelist);
return 1;
}
return 0;
}
static inline
int ctx_check_death_locked_pf(struct ptlrpc_cli_ctx *ctx,
struct hlist_head *freelist)
{
LASSERT(ctx->cc_sec);
LASSERT(atomic_read(&ctx->cc_refcount) > 0);
LASSERT(test_bit(PTLRPC_CTX_CACHED_BIT, &ctx->cc_flags));
return ctx_check_death_pf(ctx, freelist);
}
static inline
int ctx_match_pf(struct ptlrpc_cli_ctx *ctx, struct vfs_cred *vcred)
{
/* a little bit optimization for null policy */
if (!ctx->cc_ops->match)
return 1;
return ctx->cc_ops->match(ctx, vcred);
}
static
void ctx_list_destroy_pf(struct hlist_head *head)
{
struct ptlrpc_cli_ctx *ctx;
while (!hlist_empty(head)) {
ctx = hlist_entry(head->first, struct ptlrpc_cli_ctx,
cc_cache);
LASSERT(atomic_read(&ctx->cc_refcount) == 0);
LASSERT(test_bit(PTLRPC_CTX_CACHED_BIT,
&ctx->cc_flags) == 0);
hlist_del_init(&ctx->cc_cache);
ctx_destroy_pf(ctx->cc_sec, ctx);
}
}
/****************************************
* context apis *
****************************************/
static
int gss_cli_ctx_validate_pf(struct ptlrpc_cli_ctx *ctx)
{
if (ctx_check_death_pf(ctx, NULL))
return 1;
if (cli_ctx_is_ready(ctx))
return 0;
return 1;
}
static
void gss_cli_ctx_die_pf(struct ptlrpc_cli_ctx *ctx, int grace)
{
LASSERT(ctx->cc_sec);
LASSERT(atomic_read(&ctx->cc_refcount) > 0);
cli_ctx_expire(ctx);
spin_lock(&ctx->cc_sec->ps_lock);
if (test_and_clear_bit(PTLRPC_CTX_CACHED_BIT, &ctx->cc_flags)) {
LASSERT(!hlist_unhashed(&ctx->cc_cache));
LASSERT(atomic_read(&ctx->cc_refcount) > 1);
hlist_del_init(&ctx->cc_cache);
if (atomic_dec_and_test(&ctx->cc_refcount))
LBUG();
}
spin_unlock(&ctx->cc_sec->ps_lock);
}
/****************************************
* reverse context installation *
****************************************/
static inline
unsigned int ctx_hash_index(int hashsize, __u64 key)
{
return (unsigned int) (key & ((__u64) hashsize - 1));
}
static
void gss_sec_ctx_replace_pf(struct gss_sec *gsec,
struct ptlrpc_cli_ctx *new)
{
struct gss_sec_pipefs *gsec_pf;
struct ptlrpc_cli_ctx *ctx;
struct hlist_node *next;
HLIST_HEAD(freelist);
unsigned int hash;
gsec_pf = container_of(gsec, struct gss_sec_pipefs, gsp_base);
hash = ctx_hash_index(gsec_pf->gsp_chash_size,
(__u64) new->cc_vcred.vc_uid);
LASSERT(hash < gsec_pf->gsp_chash_size);
spin_lock(&gsec->gs_base.ps_lock);
hlist_for_each_entry_safe(ctx, next,
&gsec_pf->gsp_chash[hash], cc_cache) {
if (!ctx_match_pf(ctx, &new->cc_vcred))
continue;
cli_ctx_expire(ctx);
ctx_unhash_pf(ctx, &freelist);
break;
}
ctx_enhash_pf(new, &gsec_pf->gsp_chash[hash]);
spin_unlock(&gsec->gs_base.ps_lock);
ctx_list_destroy_pf(&freelist);
}
static
int gss_install_rvs_cli_ctx_pf(struct gss_sec *gsec,
struct ptlrpc_svc_ctx *svc_ctx)
{
struct vfs_cred vcred;
struct ptlrpc_cli_ctx *cli_ctx;
int rc;
vcred.vc_uid = 0;
vcred.vc_gid = 0;
cli_ctx = ctx_create_pf(&gsec->gs_base, &vcred);
if (!cli_ctx)
return -ENOMEM;
rc = gss_copy_rvc_cli_ctx(cli_ctx, svc_ctx);
if (rc) {
ctx_destroy_pf(cli_ctx->cc_sec, cli_ctx);
return rc;
}
gss_sec_ctx_replace_pf(gsec, cli_ctx);
return 0;
}
static
void gss_ctx_cache_gc_pf(struct gss_sec_pipefs *gsec_pf,
struct hlist_head *freelist)
{
struct ptlrpc_sec *sec;
struct ptlrpc_cli_ctx *ctx;
struct hlist_node *next;
int i;
sec = &gsec_pf->gsp_base.gs_base;
CDEBUG(D_SEC, "do gc on sec %s@%p\n", sec->ps_policy->sp_name, sec);
for (i = 0; i < gsec_pf->gsp_chash_size; i++) {
hlist_for_each_entry_safe(ctx, next,
&gsec_pf->gsp_chash[i], cc_cache)
ctx_check_death_locked_pf(ctx, freelist);
}
sec->ps_gc_next = cfs_time_current_sec() + sec->ps_gc_interval;
}
static
struct ptlrpc_sec* gss_sec_create_pf(struct obd_import *imp,
struct ptlrpc_svc_ctx *ctx,
struct sptlrpc_flavor *sf)
{
struct gss_sec_pipefs *gsec_pf;
int alloc_size, hash_size, i;
#define GSS_SEC_PIPEFS_CTX_HASH_SIZE (32)
if (ctx ||
sf->sf_flags & (PTLRPC_SEC_FL_ROOTONLY | PTLRPC_SEC_FL_REVERSE))
hash_size = 1;
else
hash_size = GSS_SEC_PIPEFS_CTX_HASH_SIZE;
alloc_size = sizeof(*gsec_pf) +
sizeof(struct hlist_head) * hash_size;
OBD_ALLOC(gsec_pf, alloc_size);
if (!gsec_pf)
return NULL;
gsec_pf->gsp_chash_size = hash_size;
for (i = 0; i < hash_size; i++)
INIT_HLIST_HEAD(&gsec_pf->gsp_chash[i]);
if (gss_sec_create_common(&gsec_pf->gsp_base, &gss_policy_pipefs,
imp, ctx, sf))
goto err_free;
if (ctx == NULL) {
if (gss_sec_pipe_upcall_init(&gsec_pf->gsp_base))
goto err_destroy;
} else {
if (gss_install_rvs_cli_ctx_pf(&gsec_pf->gsp_base, ctx))
goto err_destroy;
}
return &gsec_pf->gsp_base.gs_base;
err_destroy:
gss_sec_destroy_common(&gsec_pf->gsp_base);
err_free:
OBD_FREE(gsec_pf, alloc_size);
return NULL;
}
static
void gss_sec_destroy_pf(struct ptlrpc_sec *sec)
{
struct gss_sec_pipefs *gsec_pf;
struct gss_sec *gsec;
CWARN("destroy %s@%p\n", sec->ps_policy->sp_name, sec);
gsec = container_of(sec, struct gss_sec, gs_base);
gsec_pf = container_of(gsec, struct gss_sec_pipefs, gsp_base);
LASSERT(gsec_pf->gsp_chash);
LASSERT(gsec_pf->gsp_chash_size);
gss_sec_pipe_upcall_fini(gsec);
gss_sec_destroy_common(gsec);
OBD_FREE(gsec, sizeof(*gsec_pf) +
sizeof(struct hlist_head) * gsec_pf->gsp_chash_size);
}
static
struct ptlrpc_cli_ctx * gss_sec_lookup_ctx_pf(struct ptlrpc_sec *sec,
struct vfs_cred *vcred,
int create, int remove_dead)
{
struct gss_sec *gsec;
struct gss_sec_pipefs *gsec_pf;
struct ptlrpc_cli_ctx *ctx = NULL, *new = NULL;
struct hlist_head *hash_head;
struct hlist_node *next;
HLIST_HEAD(freelist);
unsigned int hash, gc = 0, found = 0;
might_sleep();
gsec = container_of(sec, struct gss_sec, gs_base);
gsec_pf = container_of(gsec, struct gss_sec_pipefs, gsp_base);
hash = ctx_hash_index(gsec_pf->gsp_chash_size,
(__u64) vcred->vc_uid);
hash_head = &gsec_pf->gsp_chash[hash];
LASSERT(hash < gsec_pf->gsp_chash_size);
retry:
spin_lock(&sec->ps_lock);
/* gc_next == 0 means never do gc */
if (remove_dead && sec->ps_gc_next &&
cfs_time_after(cfs_time_current_sec(), sec->ps_gc_next)) {
gss_ctx_cache_gc_pf(gsec_pf, &freelist);
gc = 1;
}
hlist_for_each_entry_safe(ctx, next, hash_head, cc_cache) {
if (gc == 0 &&
ctx_check_death_locked_pf(ctx,
remove_dead ? &freelist : NULL))
continue;
if (ctx_match_pf(ctx, vcred)) {
found = 1;
break;
}
}
if (found) {
if (new && new != ctx) {
/* lost the race, just free it */
hlist_add_head(&new->cc_cache, &freelist);
new = NULL;
}
/* hot node, move to head */
if (hash_head->first != &ctx->cc_cache) {
__hlist_del(&ctx->cc_cache);
hlist_add_head(&ctx->cc_cache, hash_head);
}
} else {
/* don't allocate for reverse sec */
if (sec_is_reverse(sec)) {
spin_unlock(&sec->ps_lock);
return NULL;
}
if (new) {
ctx_enhash_pf(new, hash_head);
ctx = new;
} else if (create) {
spin_unlock(&sec->ps_lock);
new = ctx_create_pf(sec, vcred);
if (new) {
clear_bit(PTLRPC_CTX_NEW_BIT, &new->cc_flags);
goto retry;
}
} else {
ctx = NULL;
}
}
/* hold a ref */
if (ctx)
atomic_inc(&ctx->cc_refcount);
spin_unlock(&sec->ps_lock);
/* the allocator of the context must give the first push to refresh */
if (new) {
LASSERT(new == ctx);
gss_cli_ctx_refresh_pf(new);
}
ctx_list_destroy_pf(&freelist);
return ctx;
}
static
void gss_sec_release_ctx_pf(struct ptlrpc_sec *sec,
struct ptlrpc_cli_ctx *ctx,
int sync)
{
LASSERT(test_bit(PTLRPC_CTX_CACHED_BIT, &ctx->cc_flags) == 0);
LASSERT(hlist_unhashed(&ctx->cc_cache));
/* if required async, we must clear the UPTODATE bit to prevent extra
* rpcs during destroy procedure. */
if (!sync)
clear_bit(PTLRPC_CTX_UPTODATE_BIT, &ctx->cc_flags);
/* destroy this context */
ctx_destroy_pf(sec, ctx);
}
/*
* @uid: which user. "-1" means flush all.
* @grace: mark context DEAD, allow graceful destroy like notify
* server side, etc.
* @force: also flush busy entries.
*
* return the number of busy context encountered.
*
* In any cases, never touch "eternal" contexts.
*/
static
int gss_sec_flush_ctx_cache_pf(struct ptlrpc_sec *sec,
uid_t uid,
int grace, int force)
{
struct gss_sec *gsec;
struct gss_sec_pipefs *gsec_pf;
struct ptlrpc_cli_ctx *ctx;
struct hlist_node *next;
HLIST_HEAD(freelist);
int i, busy = 0;
might_sleep_if(grace);
gsec = container_of(sec, struct gss_sec, gs_base);
gsec_pf = container_of(gsec, struct gss_sec_pipefs, gsp_base);
spin_lock(&sec->ps_lock);
for (i = 0; i < gsec_pf->gsp_chash_size; i++) {
hlist_for_each_entry_safe(ctx, next,
&gsec_pf->gsp_chash[i],
cc_cache) {
LASSERT(atomic_read(&ctx->cc_refcount) > 0);
if (uid != -1 && uid != ctx->cc_vcred.vc_uid)
continue;
if (atomic_read(&ctx->cc_refcount) > 1) {
busy++;
if (!force)
continue;
CWARN("flush busy(%d) ctx %p(%u->%s) by force, "
"grace %d\n",
atomic_read(&ctx->cc_refcount),
ctx, ctx->cc_vcred.vc_uid,
sec2target_str(ctx->cc_sec), grace);
}
ctx_unhash_pf(ctx, &freelist);
set_bit(PTLRPC_CTX_DEAD_BIT, &ctx->cc_flags);
if (!grace)
clear_bit(PTLRPC_CTX_UPTODATE_BIT,
&ctx->cc_flags);
}
}
spin_unlock(&sec->ps_lock);
ctx_list_destroy_pf(&freelist);
return busy;
}
/****************************************
* service apis *
****************************************/
static
int gss_svc_accept_pf(struct ptlrpc_request *req)
{
return gss_svc_accept(&gss_policy_pipefs, req);
}
static
int gss_svc_install_rctx_pf(struct obd_import *imp,
struct ptlrpc_svc_ctx *ctx)
{
struct ptlrpc_sec *sec;
int rc;
sec = sptlrpc_import_sec_ref(imp);
LASSERT(sec);
rc = gss_install_rvs_cli_ctx_pf(sec2gsec(sec), ctx);
sptlrpc_sec_put(sec);
return rc;
}
/****************************************
* rpc_pipefs definitions *
****************************************/
#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 messages 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_pipefs upcall helpers *
****************************************/
static
void gss_release_msg(struct gss_upcall_msg *gmsg)
{
LASSERT(atomic_read(&gmsg->gum_refcount) > 0);
if (!atomic_dec_and_test(&gmsg->gum_refcount)) {
return;
}
if (gmsg->gum_gctx) {
sptlrpc_cli_ctx_wakeup(&gmsg->gum_gctx->gc_base);
sptlrpc_cli_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_PTR(gmsg);
}
static
void gss_unhash_msg_nolock(struct gss_upcall_msg *gmsg)
{
__u32 idx = gmsg->gum_mechidx;
LASSERT(idx < MECH_MAX);
assert_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_cli_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_pipefs 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;
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;
mechidx = (__u32) (long) 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_cli_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;
LASSERT(list_empty(&msg->list));
/* normally errno is >= 0 */
if (msg->errno >= 0) {
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 "LPX64", obd %.*s): "
"errno %d\n", msg, gumd->gum_seq, gumd->gum_uid, gumd->gum_svc,
gumd->gum_nid, (int) 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);
}
static
void gss_pipe_release(struct inode *inode)
{
struct rpc_inode *rpci = RPC_I(inode);
__u32 idx;
idx = (__u32) (long) 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 "LPX64", obd %.*s\n", gmsg,
gumd->gum_seq, gumd->gum_uid, gumd->gum_svc,
gumd->gum_nid, (int) 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);
}
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
int gss_ctx_refresh_pf(struct ptlrpc_cli_ctx *ctx)
{
struct obd_import *imp;
struct gss_sec *gsec;
struct gss_upcall_msg *gmsg;
int rc = 0;
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_PTR(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_cli_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. */
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_PTR(gmsg);
return rc;
}
static
int gss_cli_ctx_refresh_pf(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_pf(ctx);
}
/****************************************
* lustre gss pipefs policy *
****************************************/
static struct ptlrpc_ctx_ops gss_pipefs_ctxops = {
.match = gss_cli_ctx_match,
.refresh = gss_cli_ctx_refresh_pf,
.validate = gss_cli_ctx_validate_pf,
.die = gss_cli_ctx_die_pf,
.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,
};
static struct ptlrpc_sec_cops gss_sec_pipefs_cops = {
.create_sec = gss_sec_create_pf,
.destroy_sec = gss_sec_destroy_pf,
.kill_sec = gss_sec_kill,
.lookup_ctx = gss_sec_lookup_ctx_pf,
.release_ctx = gss_sec_release_ctx_pf,
.flush_ctx_cache = gss_sec_flush_ctx_cache_pf,
.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,
.enlarge_reqbuf = gss_enlarge_reqbuf,
};
static struct ptlrpc_sec_sops gss_sec_pipefs_sops = {
.accept = gss_svc_accept_pf,
.invalidate_ctx = gss_svc_invalidate_ctx,
.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_pf,
};
static struct ptlrpc_sec_policy gss_policy_pipefs = {
.sp_owner = THIS_MODULE,
.sp_name = "gss.pipefs",
.sp_policy = SPTLRPC_POLICY_GSS_PIPEFS,
.sp_cops = &gss_sec_pipefs_cops,
.sp_sops = &gss_sec_pipefs_sops,
};
static
int __init gss_init_pipefs_upcall(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. */
/* 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]);
spin_lock_init(&upcall_locks[MECH_KRB5]);
return 0;
}
static
void __exit gss_exit_pipefs_upcall(void)
{
__u32 i;
for (i = 0; i < MECH_MAX; i++) {
LASSERT(list_empty(&upcall_lists[i]));
/* dput pipe dentry here might cause lgssd oops. */
de_pipes[i] = NULL;
}
rpc_unlink(LUSTRE_PIPE_KRB5);
rpc_rmdir(LUSTRE_PIPE_ROOT);
}
int __init gss_init_pipefs(void)
{
int rc;
rc = gss_init_pipefs_upcall();
if (rc)
return rc;
rc = sptlrpc_register_policy(&gss_policy_pipefs);
if (rc) {
gss_exit_pipefs_upcall();
return rc;
}
return 0;
}
void __exit gss_exit_pipefs(void)
{
gss_exit_pipefs_upcall();
sptlrpc_unregister_policy(&gss_policy_pipefs);
}
/*
* GPL HEADER START
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 only,
* as published by the Free Software Foundation.
*
* This program 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 version 2 for more details (a copy is included
* in the LICENSE file that accompanied this code).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; If not, see
* http://www.sun.com/software/products/lustre/docs/GPLv2.pdf
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*
* GPL HEADER END
*/
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
* Use is subject to license terms.
*
* Copyright (c) 2011, Intel Corporation.
*/
/*
* This file is part of Lustre, http://www.lustre.org/
* Lustre is a trademark of Sun Microsystems, Inc.
*
* lustre/ptlrpc/gss/gss_rawobj.c
*
* Author: Eric Mei <ericm@clusterfs.com>
*/
#define DEBUG_SUBSYSTEM S_SEC
#include <linux/mutex.h>
#include <obd.h>
#include <obd_class.h>
#include <obd_support.h>
#include <lustre_sec.h>
#include "gss_internal.h"
int rawobj_empty(rawobj_t *obj)
{
LASSERT(equi(obj->len, obj->data));
return (obj->len == 0);
}
int rawobj_alloc(rawobj_t *obj, char *buf, int len)
{
LASSERT(obj);
LASSERT(len >= 0);
obj->len = len;
if (len) {
OBD_ALLOC_LARGE(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_LARGE(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_LARGE(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 = cfs_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 : cfs_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_LARGE(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_extract_local_alloc(rawobj_t *obj, __u32 **buf, __u32 *buflen)
{
return __rawobj_extract(obj, buf, buflen, 1, 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_LARGE(rawobj->data, netobj->len);
if (rawobj->data == NULL)
return -ENOMEM;
rawobj->len = netobj->len;
memcpy(rawobj->data, netobj->data, netobj->len);
return 0;
}
/****************************************
* misc more *
****************************************/
int buffer_extract_bytes(const void **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;
}
/*
* Modifications for Lustre
*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Copyright (c) 2011, 2012, Intel Corporation.
*
* Author: Eric Mei <ericm@clusterfs.com>
*/
/*
* Neil Brown <neilb@cse.unsw.edu.au>
* J. Bruce Fields <bfields@umich.edu>
* Andy Adamson <andros@umich.edu>
* Dug Song <dugsong@monkey.org>
*
* 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
#include <linux/types.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/hash.h>
#include <linux/mutex.h>
#include <linux/sunrpc/cache.h>
#include <obd.h>
#include <obd_class.h>
#include <obd_support.h>
#include <lustre/lustre_idl.h>
#include <lustre_net.h>
#include <lustre_import.h>
#include <lustre_sec.h>
#include "gss_err.h"
#include "gss_internal.h"
#include "gss_api.h"
#define GSS_SVC_UPCALL_TIMEOUT (20)
static spinlock_t __ctx_index_lock;
static __u64 __ctx_index;
__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_update(struct rsi *new, struct rsi *old);
static struct rsi *rsi_lookup(struct rsi *item);
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_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_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 int rsi_upcall(struct cache_detail *cd, struct cache_head *h)
{
return sunrpc_cache_pipe_upcall(cd, h, rsi_request);
}
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 void rsi_put(struct kref *ref)
{
struct rsi *rsi = container_of(ref, struct rsi, h.ref);
LASSERT(rsi->h.next == NULL);
rsi_free(rsi);
OBD_FREE_PTR(rsi);
}
static int rsi_match(struct cache_head *a, struct cache_head *b)
{
struct rsi *item = container_of(a, struct rsi, h);
struct rsi *tmp = container_of(b, struct rsi, h);
return __rsi_match(item, tmp);
}
static void rsi_init(struct cache_head *cnew, struct cache_head *citem)
{
struct rsi *new = container_of(cnew, struct rsi, h);
struct rsi *item = container_of(citem, struct rsi, h);
__rsi_init(new, item);
}
static void update_rsi(struct cache_head *cnew, struct cache_head *citem)
{
struct rsi *new = container_of(cnew, struct rsi, h);
struct rsi *item = container_of(citem, struct rsi, h);
__rsi_update(new, item);
}
static struct cache_head *rsi_alloc(void)
{
struct rsi *rsi;
OBD_ALLOC_PTR(rsi);
if (rsi)
return &rsi->h;
else
return NULL;
}
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;
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;
}
rsip = rsi_lookup(&rsii);
if (!rsip)
goto out;
rsii.h.flags = 0;
/* 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_update(&rsii, rsip);
status = 0;
out:
rsi_free(&rsii);
if (rsip) {
wake_up_all(&rsip->waitq);
cache_put(&rsip->h, &rsi_cache);
} else {
status = -ENOMEM;
}
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.sptlrpc.init",
.cache_put = rsi_put,
.cache_upcall = rsi_upcall,
.cache_parse = rsi_parse,
.match = rsi_match,
.init = rsi_init,
.update = update_rsi,
.alloc = rsi_alloc,
};
static struct rsi *rsi_lookup(struct rsi *item)
{
struct cache_head *ch;
int hash = rsi_hash(item);
ch = sunrpc_cache_lookup(&rsi_cache, &item->h, hash);
if (ch)
return container_of(ch, struct rsi, h);
else
return NULL;
}
static struct rsi *rsi_update(struct rsi *new, struct rsi *old)
{
struct cache_head *ch;
int hash = rsi_hash(new);
ch = sunrpc_cache_update(&rsi_cache, &new->h, &old->h, hash);
if (ch)
return container_of(ch, struct rsi, h);
else
return NULL;
}
/****************************************
* 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_update(struct rsc *new, struct rsc *old);
static struct rsc *rsc_lookup(struct rsc *item);
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 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 void rsc_put(struct kref *ref)
{
struct rsc *rsci = container_of(ref, struct rsc, h.ref);
LASSERT(rsci->h.next == NULL);
rsc_free(rsci);
OBD_FREE_PTR(rsci);
}
static int rsc_match(struct cache_head *a, struct cache_head *b)
{
struct rsc *new = container_of(a, struct rsc, h);
struct rsc *tmp = container_of(b, struct rsc, h);
return __rsc_match(new, tmp);
}
static void rsc_init(struct cache_head *cnew, struct cache_head *ctmp)
{
struct rsc *new = container_of(cnew, struct rsc, h);
struct rsc *tmp = container_of(ctmp, struct rsc, h);
__rsc_init(new, tmp);
}
static void update_rsc(struct cache_head *cnew, struct cache_head *ctmp)
{
struct rsc *new = container_of(cnew, struct rsc, h);
struct rsc *tmp = container_of(ctmp, struct rsc, h);
__rsc_update(new, tmp);
}
static struct cache_head * rsc_alloc(void)
{
struct rsc *rsc;
OBD_ALLOC_PTR(rsc);
if (rsc)
return &rsc->h;
else
return NULL;
}
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;
struct gss_api_mech *gm = NULL;
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);
/* oss user flag */
rv = get_int(&mesg, &tmp_int);
if (rv) {
CERROR("fail to get oss user flag\n");
goto out;
}
rsci.ctx.gsc_usr_oss = (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;
}
rscp = rsc_lookup(&rsci);
if (!rscp)
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 {
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)
goto out;
tmp_buf.len = len;
tmp_buf.data = (unsigned char *)buf;
if (lgss_import_sec_context(&tmp_buf, gm,
&rsci.ctx.gsc_mechctx))
goto out;
/* currently the expiry time passed down from user-space
* is invalid, here we retrieve it from mech. */
if (lgss_inquire_context(rsci.ctx.gsc_mechctx, &ctx_expiry)) {
CERROR("unable to get expire time, drop it\n");
goto out;
}
expiry = (time_t) ctx_expiry;
}
rsci.h.expiry_time = expiry;
rscp = rsc_update(&rsci, rscp);
status = 0;
out:
if (gm)
lgss_mech_put(gm);
rsc_free(&rsci);
if (rscp)
cache_put(&rscp->h, &rsc_cache);
else
status = -ENOMEM;
if (status)
CERROR("parse rsc error %d\n", status);
return status;
}
static struct cache_detail rsc_cache = {
.hash_size = RSC_HASHMAX,
.hash_table = rsc_table,
.name = "auth.sptlrpc.context",
.cache_put = rsc_put,
.cache_parse = rsc_parse,
.match = rsc_match,
.init = rsc_init,
.update = update_rsc,
.alloc = rsc_alloc,
};
static struct rsc *rsc_lookup(struct rsc *item)
{
struct cache_head *ch;
int hash = rsc_hash(item);
ch = sunrpc_cache_lookup(&rsc_cache, &item->h, hash);
if (ch)
return container_of(ch, struct rsc, h);
else
return NULL;
}
static struct rsc *rsc_update(struct rsc *new, struct rsc *old)
{
struct cache_head *ch;
int hash = rsc_hash(new);
ch = sunrpc_cache_update(&rsc_cache, &new->h, &old->h, hash);
if (ch)
return container_of(ch, struct rsc, h);
else
return NULL;
}
#define COMPAT_RSC_PUT(item, cd) cache_put((item), (cd))
/****************************************
* 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;
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);
COMPAT_RSC_PUT(&rscp->h, &rsc_cache);
rsc_cache.entries--;
}
}
write_unlock(&rsc_cache.hash_lock);
}
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 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);
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 = NULL;
unsigned long ctx_expiry;
__u32 major;
int rc;
memset(&rsci, 0, sizeof(rsci));
if (rawobj_alloc(&rsci.handle, (char *) &gsec->gs_rvs_hdl,
sizeof(gsec->gs_rvs_hdl)))
GOTO(out, rc = -ENOMEM);
rscp = rsc_lookup(&rsci);
if (rscp == NULL)
GOTO(out, rc = -ENOMEM);
major = lgss_copy_reverse_context(gctx->gc_mechctx,
&rsci.ctx.gsc_mechctx);
if (major != GSS_S_COMPLETE)
GOTO(out, rc = -ENOMEM);
if (lgss_inquire_context(rsci.ctx.gsc_mechctx, &ctx_expiry)) {
CERROR("unable to get expire time, drop it\n");
GOTO(out, rc = -EINVAL);
}
rsci.h.expiry_time = (time_t) ctx_expiry;
if (strcmp(imp->imp_obd->obd_type->typ_name, LUSTRE_MDC_NAME) == 0)
rsci.ctx.gsc_usr_mds = 1;
else if (strcmp(imp->imp_obd->obd_type->typ_name, LUSTRE_OSC_NAME) == 0)
rsci.ctx.gsc_usr_oss = 1;
else
rsci.ctx.gsc_usr_root = 1;
rscp = rsc_update(&rsci, rscp);
if (rscp == NULL)
GOTO(out, rc = -ENOMEM);
rscp->target = imp->imp_obd;
rawobj_dup(&gctx->gc_svc_handle, &rscp->handle);
CWARN("create reverse svc ctx %p to %s: idx "LPX64"\n",
&rscp->ctx, obd2cli_tgt(imp->imp_obd), gsec->gs_rvs_hdl);
rc = 0;
out:
if (rscp)
cache_put(&rscp->h, &rsc_cache);
rsc_free(&rsci);
if (rc)
CERROR("create reverse svc ctx: idx "LPX64", rc %d\n",
gsec->gs_rvs_hdl, rc);
return rc;
}
int gss_svc_upcall_expire_rvs_ctx(rawobj_t *handle)
{
const cfs_time_t expire = 20;
struct rsc *rscp;
rscp = gss_svc_searchbyctx(handle);
if (rscp) {
CDEBUG(D_SEC, "reverse svcctx %p (rsc %p) expire soon\n",
&rscp->ctx, rscp);
rscp->h.expiry_time = cfs_time_current_sec() + expire;
COMPAT_RSC_PUT(&rscp->h, &rsc_cache);
}
return 0;
}
int gss_svc_upcall_dup_handle(rawobj_t *handle, struct gss_svc_ctx *ctx)
{
struct rsc *rscp = container_of(ctx, struct rsc, ctx);
return rawobj_dup(handle, &rscp->handle);
}
int gss_svc_upcall_update_sequence(rawobj_t *handle, __u32 seq)
{
struct rsc *rscp;
rscp = gss_svc_searchbyctx(handle);
if (rscp) {
CDEBUG(D_SEC, "reverse svcctx %p (rsc %p) update seq to %u\n",
&rscp->ctx, rscp, seq + 1);
rscp->ctx.gsc_rvs_seq = seq + 1;
COMPAT_RSC_PUT(&rscp->h, &rsc_cache);
}
return 0;
}
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;
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);
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)
schedule_timeout(GSS_SVC_UPCALL_TIMEOUT *
HZ);
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;
CDEBUG(D_SEC, "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 = cfs_size_round4(rsip->out_token.len);
rc = lustre_pack_reply_v2(req, 1, &replen, NULL, 0);
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);
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);
else
CDEBUG(D_SEC, "create rsc with idx "LPX64"\n",
gss_handle_to_u64(&rsci->handle));
COMPAT_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 ctx idx "LPX64" from %s\n",
gss_handle_to_u64(&gw->gw_handle),
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);
COMPAT_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);
/* can't be found */
set_bit(CACHE_NEGATIVE, &rsc->h.flags);
/* to be removed at next scan */
rsc->h.expiry_time = 1;
}
int __init gss_init_svc_upcall(void)
{
int i;
spin_lock_init(&__ctx_index_lock);
/*
* this helps reducing context index confliction. after server reboot,
* conflicting request from clients might be filtered out by initial
* sequence number checking, thus no chance to sent error notification
* back to clients.
*/
cfs_get_random_bytes(&__ctx_index, sizeof(__ctx_index));
cache_register(&rsi_cache);
cache_register(&rsc_cache);
/* FIXME this looks stupid. we intend to give lsvcgssd a chance to open
* the init upcall channel, otherwise there's big chance that the first
* upcall issued before the channel be opened thus nfsv4 cache code will
* drop the request direclty, thus lead to unnecessary recovery time.
* here we wait at maximum 1.5 seconds. */
for (i = 0; i < 6; i++) {
if (atomic_read(&rsi_cache.readers) > 0)
break;
set_current_state(TASK_UNINTERRUPTIBLE);
LASSERT(HZ >= 4);
schedule_timeout(HZ / 4);
}
if (atomic_read(&rsi_cache.readers) == 0)
CWARN("Init channel is not opened by lsvcgssd, following "
"request might be dropped until lsvcgssd is active\n");
return 0;
}
void __exit gss_exit_svc_upcall(void)
{
cache_purge(&rsi_cache);
cache_unregister(&rsi_cache);
cache_purge(&rsc_cache);
cache_unregister(&rsc_cache);
}
/*
* GPL HEADER START
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 only,
* as published by the Free Software Foundation.
*
* This program 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 version 2 for more details (a copy is included
* in the LICENSE file that accompanied this code).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; If not, see
* http://www.sun.com/software/products/lustre/docs/GPLv2.pdf
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*
* GPL HEADER END
*/
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
* Use is subject to license terms.
*
* Copyright (c) 2012, Intel Corporation.
*/
/*
* This file is part of Lustre, http://www.lustre.org/
* Lustre is a trademark of Sun Microsystems, Inc.
*/
#define DEBUG_SUBSYSTEM S_SEC
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/dcache.h>
#include <linux/fs.h>
#include <linux/mutex.h>
#include <obd.h>
#include <obd_class.h>
#include <obd_support.h>
#include <lustre/lustre_idl.h>
#include <lustre_net.h>
#include <lustre_import.h>
#include <lprocfs_status.h>
#include <lustre_sec.h>
#include "gss_err.h"
#include "gss_internal.h"
#include "gss_api.h"
static struct proc_dir_entry *gss_proc_root = NULL;
static struct proc_dir_entry *gss_proc_lk = NULL;
/*
* statistic of "out-of-sequence-window"
*/
static struct {
spinlock_t oos_lock;
atomic_t oos_cli_count; /* client occurrence */
int oos_cli_behind; /* client max seqs behind */
atomic_t oos_svc_replay[3]; /* server replay detected */
atomic_t oos_svc_pass[3]; /* server verified ok */
} gss_stat_oos = {
.oos_cli_count = ATOMIC_INIT(0),
.oos_cli_behind = 0,
.oos_svc_replay = { ATOMIC_INIT(0), },
.oos_svc_pass = { ATOMIC_INIT(0), },
};
void gss_stat_oos_record_cli(int behind)
{
atomic_inc(&gss_stat_oos.oos_cli_count);
spin_lock(&gss_stat_oos.oos_lock);
if (behind > gss_stat_oos.oos_cli_behind)
gss_stat_oos.oos_cli_behind = behind;
spin_unlock(&gss_stat_oos.oos_lock);
}
void gss_stat_oos_record_svc(int phase, int replay)
{
LASSERT(phase >= 0 && phase <= 2);
if (replay)
atomic_inc(&gss_stat_oos.oos_svc_replay[phase]);
else
atomic_inc(&gss_stat_oos.oos_svc_pass[phase]);
}
static int gss_proc_oos_seq_show(struct seq_file *m, void *v)
{
return seq_printf(m,
"seqwin: %u\n"
"backwin: %u\n"
"client fall behind seqwin\n"
" occurrence: %d\n"
" max seq behind: %d\n"
"server replay detected:\n"
" phase 0: %d\n"
" phase 1: %d\n"
" phase 2: %d\n"
"server verify ok:\n"
" phase 2: %d\n",
GSS_SEQ_WIN_MAIN,
GSS_SEQ_WIN_BACK,
atomic_read(&gss_stat_oos.oos_cli_count),
gss_stat_oos.oos_cli_behind,
atomic_read(&gss_stat_oos.oos_svc_replay[0]),
atomic_read(&gss_stat_oos.oos_svc_replay[1]),
atomic_read(&gss_stat_oos.oos_svc_replay[2]),
atomic_read(&gss_stat_oos.oos_svc_pass[2]));
}
LPROC_SEQ_FOPS_RO(gss_proc_oos);
static int gss_proc_write_secinit(struct file *file, const char *buffer,
size_t count, off_t *off)
{
int rc;
rc = gss_do_ctx_init_rpc((char *) buffer, count);
if (rc) {
LASSERT(rc < 0);
return rc;
}
return count;
}
static const struct file_operations gss_proc_secinit = {
.write = gss_proc_write_secinit,
};
static struct lprocfs_vars gss_lprocfs_vars[] = {
{ "replays", &gss_proc_oos_fops },
{ "init_channel", &gss_proc_secinit, NULL, 0222 },
{ NULL }
};
/*
* for userspace helper lgss_keyring.
*
* debug_level: [0, 4], defined in utils/gss/lgss_utils.h
*/
static int gss_lk_debug_level = 1;
static int gss_lk_proc_dl_seq_show(struct seq_file *m, void *v)
{
return seq_printf(m, "%u\n", gss_lk_debug_level);
}
static int gss_lk_proc_dl_seq_write(struct file *file, const char *buffer,
size_t count, off_t *off)
{
int val, rc;
rc = lprocfs_write_helper(buffer, count, &val);
if (rc < 0)
return rc;
if (val < 0 || val > 4)
return -ERANGE;
gss_lk_debug_level = val;
return count;
}
LPROC_SEQ_FOPS(gss_lk_proc_dl);
static struct lprocfs_vars gss_lk_lprocfs_vars[] = {
{ "debug_level", &gss_lk_proc_dl_fops },
{ NULL }
};
void gss_exit_lproc(void)
{
if (gss_proc_lk) {
lprocfs_remove(&gss_proc_lk);
gss_proc_lk = NULL;
}
if (gss_proc_root) {
lprocfs_remove(&gss_proc_root);
gss_proc_root = NULL;
}
}
int gss_init_lproc(void)
{
int rc;
spin_lock_init(&gss_stat_oos.oos_lock);
gss_proc_root = lprocfs_register("gss", sptlrpc_proc_root,
gss_lprocfs_vars, NULL);
if (IS_ERR(gss_proc_root)) {
rc = PTR_ERR(gss_proc_root);
gss_proc_root = NULL;
GOTO(err_out, rc);
}
gss_proc_lk = lprocfs_register("lgss_keyring", gss_proc_root,
gss_lk_lprocfs_vars, NULL);
if (IS_ERR(gss_proc_lk)) {
rc = PTR_ERR(gss_proc_lk);
gss_proc_lk = NULL;
GOTO(err_out, rc);
}
return 0;
err_out:
CERROR("failed to initialize gss lproc entries: %d\n", rc);
gss_exit_lproc();
return rc;
}
/*
* Modifications for Lustre
*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Copyright (c) 2011, 2012, Intel Corporation.
*
* Author: Eric Mei <ericm@clusterfs.com>
*/
/*
* 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 <dugsong@monkey.org>
* Andy Adamson <andros@umich.edu>
*
* 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.
*
*/
#define DEBUG_SUBSYSTEM S_SEC
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/dcache.h>
#include <linux/fs.h>
#include <linux/mutex.h>
#include <asm/atomic.h>
#include <obd.h>
#include <obd_class.h>
#include <obd_support.h>
#include <obd_cksum.h>
#include <lustre/lustre_idl.h>
#include <lustre_net.h>
#include <lustre_import.h>
#include <lustre_sec.h>
#include "gss_err.h"
#include "gss_internal.h"
#include "gss_api.h"
#include <linux/crypto.h>
#include <linux/crc32.h>
/*
* early reply have fixed size, respectively in privacy and integrity mode.
* so we calculate them only once.
*/
static int gss_at_reply_off_integ;
static int gss_at_reply_off_priv;
static inline int msg_last_segidx(struct lustre_msg *msg)
{
LASSERT(msg->lm_bufcount > 0);
return msg->lm_bufcount - 1;
}
static inline int msg_last_seglen(struct lustre_msg *msg)
{
return msg->lm_buflens[msg_last_segidx(msg)];
}
/********************************************
* wire data swabber *
********************************************/
static
void gss_header_swabber(struct gss_header *ghdr)
{
__swab32s(&ghdr->gh_flags);
__swab32s(&ghdr->gh_proc);
__swab32s(&ghdr->gh_seq);
__swab32s(&ghdr->gh_svc);
__swab32s(&ghdr->gh_pad1);
__swab32s(&ghdr->gh_handle.len);
}
struct gss_header *gss_swab_header(struct lustre_msg *msg, int segment,
int swabbed)
{
struct gss_header *ghdr;
ghdr = lustre_msg_buf(msg, segment, sizeof(*ghdr));
if (ghdr == NULL)
return NULL;
if (swabbed)
gss_header_swabber(ghdr);
if (sizeof(*ghdr) + ghdr->gh_handle.len > msg->lm_buflens[segment]) {
CERROR("gss header has length %d, now %u received\n",
(int) sizeof(*ghdr) + ghdr->gh_handle.len,
msg->lm_buflens[segment]);
return NULL;
}
return ghdr;
}
#if 0
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 %u but only %u received\n",
(unsigned int) sizeof(*obj) + obj->len,
msg->lm_buflens[segment]);
return NULL;
}
return obj;
}
#endif
/*
* payload should be obtained from mechanism. but currently since we
* only support kerberos, we could simply use fixed value.
* krb5 "meta" data:
* - krb5 header: 16
* - krb5 checksum: 20
*
* for privacy mode, payload also include the cipher text which has the same
* size as plain text, plus possible confounder, padding both at maximum cipher
* block size.
*/
#define GSS_KRB5_INTEG_MAX_PAYLOAD (40)
static inline
int gss_mech_payload(struct gss_ctx *mechctx, int msgsize, int privacy)
{
if (privacy)
return GSS_KRB5_INTEG_MAX_PAYLOAD + 16 + 16 + 16 + msgsize;
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,
enum lustre_sec_part sp,
__u32 flags, __u32 proc, __u32 seq, __u32 svc,
rawobj_t *handle)
{
struct gss_header *ghdr;
rawobj_t text[4], mic;
int textcnt, max_textcnt, mic_idx;
__u32 major;
LASSERT(msg->lm_bufcount >= 2);
/* 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_sp = (__u8) sp;
ghdr->gh_flags = flags;
ghdr->gh_proc = proc;
ghdr->gh_seq = seq;
ghdr->gh_svc = svc;
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);
}
/* no actual signature for null mode */
if (svc == SPTLRPC_SVC_NULL)
return lustre_msg_size_v2(msg->lm_bufcount, msg->lm_buflens);
/* MIC */
mic_idx = msg_last_segidx(msg);
max_textcnt = (svc == SPTLRPC_SVC_AUTH) ? 1 : mic_idx;
for (textcnt = 0; textcnt < max_textcnt; 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, 0, NULL, &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,
__u32 svc)
{
rawobj_t text[4], mic;
int textcnt, max_textcnt;
int mic_idx;
__u32 major;
LASSERT(msg->lm_bufcount >= 2);
if (svc == SPTLRPC_SVC_NULL)
return GSS_S_COMPLETE;
mic_idx = msg_last_segidx(msg);
max_textcnt = (svc == SPTLRPC_SVC_AUTH) ? 1 : mic_idx;
for (textcnt = 0; textcnt < max_textcnt; 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, 0, NULL, &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, hdrobj, token;
__u8 *clear_buf;
int clear_buflen;
__u32 major;
if (msgbuf->lm_bufcount != 2) {
CERROR("invalid bufcount %d\n", msgbuf->lm_bufcount);
return GSS_S_FAILURE;
}
/* allocate a temporary clear text buffer, same sized as token,
* we assume the final clear text size <= token size */
clear_buflen = lustre_msg_buflen(msgbuf, 1);
OBD_ALLOC_LARGE(clear_buf, clear_buflen);
if (!clear_buf)
return GSS_S_FAILURE;
/* buffer objects */
hdrobj.len = lustre_msg_buflen(msgbuf, 0);
hdrobj.data = lustre_msg_buf(msgbuf, 0, 0);
token.len = lustre_msg_buflen(msgbuf, 1);
token.data = lustre_msg_buf(msgbuf, 1, 0);
clear_obj.len = clear_buflen;
clear_obj.data = clear_buf;
major = lgss_unwrap(mechctx, &hdrobj, &token, &clear_obj);
if (major != GSS_S_COMPLETE) {
CERROR("unwrap message error: %08x\n", major);
GOTO(out_free, major = GSS_S_FAILURE);
}
LASSERT(clear_obj.len <= clear_buflen);
LASSERT(clear_obj.len <= msgbuf_len);
/* 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_LARGE(clear_buf, clear_buflen);
return major;
}
/********************************************
* gss client context manipulation helpers *
********************************************/
int cli_ctx_expire(struct ptlrpc_cli_ctx *ctx)
{
LASSERT(atomic_read(&ctx->cc_refcount));
if (!test_and_set_bit(PTLRPC_CTX_DEAD_BIT, &ctx->cc_flags)) {
if (!ctx->cc_early_expire)
clear_bit(PTLRPC_CTX_UPTODATE_BIT, &ctx->cc_flags);
CWARN("ctx %p(%u->%s) get expired: %lu(%+lds)\n",
ctx, ctx->cc_vcred.vc_uid, sec2target_str(ctx->cc_sec),
ctx->cc_expire,
ctx->cc_expire == 0 ? 0 :
cfs_time_sub(ctx->cc_expire, cfs_time_current_sec()));
sptlrpc_cli_ctx_wakeup(ctx);
return 1;
}
return 0;
}
/*
* return 1 if the context is dead.
*/
int cli_ctx_check_death(struct ptlrpc_cli_ctx *ctx)
{
if (unlikely(cli_ctx_is_dead(ctx)))
return 1;
/* expire is 0 means never expire. a newly created gss context
* which during upcall may has 0 expiration */
if (ctx->cc_expire == 0)
return 0;
/* check real expiration */
if (cfs_time_after(ctx->cc_expire, cfs_time_current_sec()))
return 0;
cli_ctx_expire(ctx);
return 1;
}
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_flvr.sf_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 destroyed. */
set_bit(PTLRPC_CTX_UPTODATE_BIT, &ctx->cc_flags);
if (sec_is_reverse(ctx->cc_sec)) {
CWARN("server installed reverse ctx %p idx "LPX64", "
"expiry %lu(%+lds)\n", ctx,
gss_handle_to_u64(&gctx->gc_handle),
ctx->cc_expire, ctx->cc_expire - cfs_time_current_sec());
} else {
CWARN("client refreshed ctx %p idx "LPX64" (%u->%s), "
"expiry %lu(%+lds)\n", ctx,
gss_handle_to_u64(&gctx->gc_handle),
ctx->cc_vcred.vc_uid, sec2target_str(ctx->cc_sec),
ctx->cc_expire, ctx->cc_expire - cfs_time_current_sec());
/* install reverse svc ctx for root context */
if (ctx->cc_vcred.vc_uid == 0)
gss_sec_install_rctx(ctx->cc_sec->ps_import,
ctx->cc_sec, ctx);
}
sptlrpc_cli_ctx_wakeup(ctx);
}
static void gss_cli_ctx_finalize(struct gss_cli_ctx *gctx)
{
LASSERT(gctx->gc_base.cc_sec);
if (gctx->gc_mechctx) {
lgss_delete_sec_context(&gctx->gc_mechctx);
gctx->gc_mechctx = NULL;
}
if (!rawobj_empty(&gctx->gc_svc_handle)) {
/* forward ctx: mark buddy reverse svcctx soon-expire. */
if (!sec_is_reverse(gctx->gc_base.cc_sec) &&
!rawobj_empty(&gctx->gc_svc_handle))
gss_svc_upcall_expire_rvs_ctx(&gctx->gc_svc_handle);
rawobj_free(&gctx->gc_svc_handle);
}
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.
*
* return value:
* 1: looks like a replay
* 0: is ok
* -1: is a replay
*
* 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) {
/*
* phase 0 testing
*/
rc = gss_do_check_seq(ssd->ssd_win_main, GSS_SEQ_WIN_MAIN,
&ssd->ssd_max_main, seq_num, 0);
if (unlikely(rc))
gss_stat_oos_record_svc(0, 1);
} else {
/*
* phase 1 checking main window
*/
rc = gss_do_check_seq(ssd->ssd_win_main, GSS_SEQ_WIN_MAIN,
&ssd->ssd_max_main, seq_num, 1);
switch (rc) {
case -1:
gss_stat_oos_record_svc(1, 1);
/* fall through */
case 0:
goto exit;
}
/*
* phase 2 checking back window
*/
rc = gss_do_check_seq(ssd->ssd_win_back, GSS_SEQ_WIN_BACK,
&ssd->ssd_max_back, seq_num, 2);
if (rc)
gss_stat_oos_record_svc(2, 1);
else
gss_stat_oos_record_svc(2, 0);
}
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_mech_payload(NULL, msgsize, privacy);
}
static int gss_cli_bulk_payload(struct ptlrpc_cli_ctx *ctx,
struct sptlrpc_flavor *flvr,
int reply, int read)
{
int payload = sizeof(struct ptlrpc_bulk_sec_desc);
LASSERT(SPTLRPC_FLVR_BULK_TYPE(flvr->sf_rpc) == SPTLRPC_BULK_DEFAULT);
if ((!reply && !read) || (reply && read)) {
switch (SPTLRPC_FLVR_BULK_SVC(flvr->sf_rpc)) {
case SPTLRPC_BULK_SVC_NULL:
break;
case SPTLRPC_BULK_SVC_INTG:
payload += gss_cli_payload(ctx, 0, 0);
break;
case SPTLRPC_BULK_SVC_PRIV:
payload += gss_cli_payload(ctx, 0, 1);
break;
case SPTLRPC_BULK_SVC_AUTH:
default:
LBUG();
}
}
return payload;
}
int gss_cli_ctx_match(struct ptlrpc_cli_ctx *ctx, struct vfs_cred *vcred)
{
return (ctx->cc_vcred.vc_uid == vcred->vc_uid);
}
void gss_cli_ctx_flags2str(unsigned long flags, char *buf, int bufsize)
{
buf[0] = '\0';
if (flags & PTLRPC_CTX_NEW)
strncat(buf, "new,", bufsize);
if (flags & PTLRPC_CTX_UPTODATE)
strncat(buf, "uptodate,", bufsize);
if (flags & PTLRPC_CTX_DEAD)
strncat(buf, "dead,", bufsize);
if (flags & PTLRPC_CTX_ERROR)
strncat(buf, "error,", bufsize);
if (flags & PTLRPC_CTX_CACHED)
strncat(buf, "cached,", bufsize);
if (flags & PTLRPC_CTX_ETERNAL)
strncat(buf, "eternal,", bufsize);
if (buf[0] == '\0')
strncat(buf, "-,", bufsize);
buf[strlen(buf) - 1] = '\0';
}
int gss_cli_ctx_sign(struct ptlrpc_cli_ctx *ctx,
struct ptlrpc_request *req)
{
struct gss_cli_ctx *gctx = ctx2gctx(ctx);
__u32 flags = 0, seq, svc;
int rc;
LASSERT(req->rq_reqbuf);
LASSERT(req->rq_reqbuf->lm_bufcount >= 2);
LASSERT(req->rq_cli_ctx == ctx);
/* nothing to do for context negotiation RPCs */
if (req->rq_ctx_init)
return 0;
svc = SPTLRPC_FLVR_SVC(req->rq_flvr.sf_rpc);
if (req->rq_pack_bulk)
flags |= LUSTRE_GSS_PACK_BULK;
if (req->rq_pack_udesc)
flags |= LUSTRE_GSS_PACK_USER;
redo:
seq = atomic_inc_return(&gctx->gc_seq);
rc = gss_sign_msg(req->rq_reqbuf, gctx->gc_mechctx,
ctx->cc_sec->ps_part,
flags, gctx->gc_proc, seq, svc,
&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().
*
* Note: null mode doesn't check sequence number. */
if (svc != SPTLRPC_SVC_NULL &&
atomic_read(&gctx->gc_seq) - seq > GSS_SEQ_REPACK_THRESHOLD) {
int behind = atomic_read(&gctx->gc_seq) - seq;
gss_stat_oos_record_cli(behind);
CWARN("req %p: %u behind, retry signing\n", req, behind);
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;
CWARN("req x"LPU64"/t"LPU64", ctx %p idx "LPX64"(%u->%s): "
"%sserver respond (%08x/%08x)\n",
req->rq_xid, req->rq_transno, ctx,
gss_handle_to_u64(&ctx2gctx(ctx)->gc_handle),
ctx->cc_vcred.vc_uid, sec2target_str(ctx->cc_sec),
sec_is_reverse(ctx->cc_sec) ? "reverse" : "",
errhdr->gh_major, errhdr->gh_minor);
/* context fini rpc, let it failed */
if (req->rq_ctx_fini) {
CWARN("context fini rpc failed\n");
return -EINVAL;
}
/* reverse sec, just return error, don't expire this ctx because it's
* crucial to callback rpcs. note if the callback rpc failed because
* of bit flip during network transfer, the client will be evicted
* directly. so more gracefully we probably want let it retry for
* number of times. */
if (sec_is_reverse(ctx->cc_sec))
return -EINVAL;
if (errhdr->gh_major != GSS_S_NO_CONTEXT &&
errhdr->gh_major != GSS_S_BAD_SIG)
return -EACCES;
/* server return NO_CONTEXT might be caused by context expire
* or server reboot/failover. we try to refresh a new ctx which
* be transparent 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. */
CWARN("%s: server might lost the context, retrying\n",
errhdr->gh_major == GSS_S_NO_CONTEXT ? "NO_CONTEXT" : "BAD_SIG");
sptlrpc_cli_ctx_expire(ctx);
/* we need replace the ctx right here, otherwise during
* resent we'll hit the logic in sptlrpc_req_refresh_ctx()
* which keep the ctx with RESEND flag, thus we'll never
* get rid of this ctx. */
rc = sptlrpc_req_replace_dead_ctx(req);
if (rc == 0)
req->rq_resend = 1;
return rc;
}
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_repdata;
__u32 major;
int pack_bulk, swabbed, rc = 0;
LASSERT(req->rq_cli_ctx == ctx);
LASSERT(msg);
gctx = container_of(ctx, struct gss_cli_ctx, gc_base);
/* special case for context negotiation, rq_repmsg/rq_replen actually
* are not used currently. but early reply always be treated normally */
if (req->rq_ctx_init && !req->rq_early) {
req->rq_repmsg = lustre_msg_buf(msg, 1, 0);
req->rq_replen = msg->lm_buflens[1];
return 0;
}
if (msg->lm_bufcount < 2 || msg->lm_bufcount > 4) {
CERROR("unexpected bufcount %u\n", msg->lm_bufcount);
return -EPROTO;
}
swabbed = ptlrpc_rep_need_swab(req);
ghdr = gss_swab_header(msg, 0, swabbed);
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:
pack_bulk = ghdr->gh_flags & LUSTRE_GSS_PACK_BULK;
if (!req->rq_early &&
!equi(req->rq_pack_bulk == 1, pack_bulk)) {
CERROR("%s bulk flag in reply\n",
req->rq_pack_bulk ? "missing" : "unexpected");
return -EPROTO;
}
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 != reqhdr->gh_svc) {
CERROR("svc %u mismatch, expect %u\n",
ghdr->gh_svc, reqhdr->gh_svc);
return -EPROTO;
}
if (swabbed)
gss_header_swabber(ghdr);
major = gss_verify_msg(msg, gctx->gc_mechctx, reqhdr->gh_svc);
if (major != GSS_S_COMPLETE) {
CERROR("failed to verify reply: %x\n", major);
return -EPERM;
}
if (req->rq_early && reqhdr->gh_svc == SPTLRPC_SVC_NULL) {
__u32 cksum;
cksum = crc32_le(!(__u32) 0,
lustre_msg_buf(msg, 1, 0),
lustre_msg_buflen(msg, 1));
if (cksum != msg->lm_cksum) {
CWARN("early reply checksum mismatch: "
"%08x != %08x\n", cksum, msg->lm_cksum);
return -EPROTO;
}
}
if (pack_bulk) {
/* bulk checksum is right after the lustre msg */
if (msg->lm_bufcount < 3) {
CERROR("Invalid reply bufcount %u\n",
msg->lm_bufcount);
return -EPROTO;
}
rc = bulk_sec_desc_unpack(msg, 2, swabbed);
if (rc) {
CERROR("unpack bulk desc: %d\n", rc);
return rc;
}
}
req->rq_repmsg = lustre_msg_buf(msg, 1, 0);
req->rq_replen = msg->lm_buflens[1];
break;
case PTLRPC_GSS_PROC_ERR:
if (req->rq_early) {
CERROR("server return error with early reply\n");
rc = -EPROTO;
} else {
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;
}
int gss_cli_ctx_seal(struct ptlrpc_cli_ctx *ctx,
struct ptlrpc_request *req)
{
struct gss_cli_ctx *gctx;
rawobj_t hdrobj, msgobj, token;
struct gss_header *ghdr;
__u32 buflens[2], major;
int wiresize, rc;
LASSERT(req->rq_clrbuf);
LASSERT(req->rq_cli_ctx == ctx);
LASSERT(req->rq_reqlen);
gctx = container_of(ctx, struct gss_cli_ctx, gc_base);
/* final 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, req->rq_clrdata_len, 1);
wiresize = lustre_msg_size_v2(2, 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_LARGE(req->rq_reqbuf, wiresize);
if (!req->rq_reqbuf)
return -ENOMEM;
req->rq_reqbuf_len = wiresize;
}
lustre_init_msg_v2(req->rq_reqbuf, 2, buflens, NULL);
req->rq_reqbuf->lm_secflvr = req->rq_flvr.sf_rpc;
/* gss header */
ghdr = lustre_msg_buf(req->rq_reqbuf, 0, 0);
ghdr->gh_version = PTLRPC_GSS_VERSION;
ghdr->gh_sp = (__u8) ctx->cc_sec->ps_part;
ghdr->gh_flags = 0;
ghdr->gh_proc = gctx->gc_proc;
ghdr->gh_svc = SPTLRPC_SVC_PRIV;
ghdr->gh_handle.len = gctx->gc_handle.len;
memcpy(ghdr->gh_handle.data, gctx->gc_handle.data, gctx->gc_handle.len);
if (req->rq_pack_bulk)
ghdr->gh_flags |= LUSTRE_GSS_PACK_BULK;
if (req->rq_pack_udesc)
ghdr->gh_flags |= LUSTRE_GSS_PACK_USER;
redo:
ghdr->gh_seq = atomic_inc_return(&gctx->gc_seq);
/* buffer objects */
hdrobj.len = PTLRPC_GSS_HEADER_SIZE;
hdrobj.data = (__u8 *) ghdr;
msgobj.len = req->rq_clrdata_len;
msgobj.data = (__u8 *) req->rq_clrbuf;
token.len = lustre_msg_buflen(req->rq_reqbuf, 1);
token.data = lustre_msg_buf(req->rq_reqbuf, 1, 0);
major = lgss_wrap(gctx->gc_mechctx, &hdrobj, &msgobj,
req->rq_clrbuf_len, &token);
if (major != GSS_S_COMPLETE) {
CERROR("priv: wrap message error: %08x\n", major);
GOTO(err_free, rc = -EPERM);
}
LASSERT(token.len <= buflens[1]);
/* see explain in gss_cli_ctx_sign() */
if (unlikely(atomic_read(&gctx->gc_seq) - ghdr->gh_seq >
GSS_SEQ_REPACK_THRESHOLD)) {
int behind = atomic_read(&gctx->gc_seq) - ghdr->gh_seq;
gss_stat_oos_record_cli(behind);
CWARN("req %p: %u behind, retry sealing\n", req, behind);
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, 1, token.len,0);
return 0;
err_free:
if (!req->rq_pool) {
OBD_FREE_LARGE(req->rq_reqbuf, req->rq_reqbuf_len);
req->rq_reqbuf = NULL;
req->rq_reqbuf_len = 0;
}
return rc;
}
int gss_cli_ctx_unseal(struct ptlrpc_cli_ctx *ctx,
struct ptlrpc_request *req)
{
struct gss_cli_ctx *gctx;
struct gss_header *ghdr;
struct lustre_msg *msg = req->rq_repdata;
int msglen, pack_bulk, swabbed, rc;
__u32 major;
LASSERT(req->rq_cli_ctx == ctx);
LASSERT(req->rq_ctx_init == 0);
LASSERT(msg);
gctx = container_of(ctx, struct gss_cli_ctx, gc_base);
swabbed = ptlrpc_rep_need_swab(req);
ghdr = gss_swab_header(msg, 0, swabbed);
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:
pack_bulk = ghdr->gh_flags & LUSTRE_GSS_PACK_BULK;
if (!req->rq_early &&
!equi(req->rq_pack_bulk == 1, pack_bulk)) {
CERROR("%s bulk flag in reply\n",
req->rq_pack_bulk ? "missing" : "unexpected");
return -EPROTO;
}
if (swabbed)
gss_header_swabber(ghdr);
/* use rq_repdata_len as buffer size, which assume unseal
* doesn't need extra memory space. for precise control, we'd
* better calculate out actual buffer size as
* (repbuf_len - offset - repdata_len) */
major = gss_unseal_msg(gctx->gc_mechctx, msg,
&msglen, req->rq_repdata_len);
if (major != GSS_S_COMPLETE) {
CERROR("failed to unwrap reply: %x\n", major);
rc = -EPERM;
break;
}
swabbed = __lustre_unpack_msg(msg, msglen);
if (swabbed < 0) {
CERROR("Failed to unpack after decryption\n");
return -EPROTO;
}
if (msg->lm_bufcount < 1) {
CERROR("Invalid reply buffer: empty\n");
return -EPROTO;
}
if (pack_bulk) {
if (msg->lm_bufcount < 2) {
CERROR("bufcount %u: missing bulk sec desc\n",
msg->lm_bufcount);
return -EPROTO;
}
/* bulk checksum is the last segment */
if (bulk_sec_desc_unpack(msg, msg->lm_bufcount - 1,
swabbed))
return -EPROTO;
}
req->rq_repmsg = lustre_msg_buf(msg, 0, 0);
req->rq_replen = msg->lm_buflens[0];
rc = 0;
break;
case PTLRPC_GSS_PROC_ERR:
if (req->rq_early) {
CERROR("server return error with early reply\n");
rc = -EPROTO;
} else {
rc = gss_cli_ctx_handle_err_notify(ctx, req, ghdr);
}
break;
default:
CERROR("unexpected proc %d\n", ghdr->gh_proc);
rc = -EPERM;
}
return rc;
}
/*********************************************
* reverse context installation *
*********************************************/
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 *
*********************************************/
int gss_sec_create_common(struct gss_sec *gsec,
struct ptlrpc_sec_policy *policy,
struct obd_import *imp,
struct ptlrpc_svc_ctx *svcctx,
struct sptlrpc_flavor *sf)
{
struct ptlrpc_sec *sec;
LASSERT(imp);
LASSERT(SPTLRPC_FLVR_POLICY(sf->sf_rpc) == SPTLRPC_POLICY_GSS);
gsec->gs_mech = lgss_subflavor_to_mech(
SPTLRPC_FLVR_BASE_SUB(sf->sf_rpc));
if (!gsec->gs_mech) {
CERROR("gss backend 0x%x not found\n",
SPTLRPC_FLVR_BASE_SUB(sf->sf_rpc));
return -EOPNOTSUPP;
}
spin_lock_init(&gsec->gs_lock);
gsec->gs_rvs_hdl = 0ULL;
/* initialize upper ptlrpc_sec */
sec = &gsec->gs_base;
sec->ps_policy = policy;
atomic_set(&sec->ps_refcount, 0);
atomic_set(&sec->ps_nctx, 0);
sec->ps_id = sptlrpc_get_next_secid();
sec->ps_flvr = *sf;
sec->ps_import = class_import_get(imp);
spin_lock_init(&sec->ps_lock);
INIT_LIST_HEAD(&sec->ps_gc_list);
if (!svcctx) {
sec->ps_gc_interval = GSS_GC_INTERVAL;
} else {
LASSERT(sec_is_reverse(sec));
/* never do gc on reverse sec */
sec->ps_gc_interval = 0;
}
if (SPTLRPC_FLVR_BULK_SVC(sec->ps_flvr.sf_rpc) == SPTLRPC_BULK_SVC_PRIV)
sptlrpc_enc_pool_add_user();
CDEBUG(D_SEC, "create %s%s@%p\n", (svcctx ? "reverse " : ""),
policy->sp_name, gsec);
return 0;
}
void gss_sec_destroy_common(struct gss_sec *gsec)
{
struct ptlrpc_sec *sec = &gsec->gs_base;
LASSERT(sec->ps_import);
LASSERT(atomic_read(&sec->ps_refcount) == 0);
LASSERT(atomic_read(&sec->ps_nctx) == 0);
if (gsec->gs_mech) {
lgss_mech_put(gsec->gs_mech);
gsec->gs_mech = NULL;
}
class_import_put(sec->ps_import);
if (SPTLRPC_FLVR_BULK_SVC(sec->ps_flvr.sf_rpc) == SPTLRPC_BULK_SVC_PRIV)
sptlrpc_enc_pool_del_user();
}
void gss_sec_kill(struct ptlrpc_sec *sec)
{
sec->ps_dying = 1;
}
int gss_cli_ctx_init_common(struct ptlrpc_sec *sec,
struct ptlrpc_cli_ctx *ctx,
struct ptlrpc_ctx_ops *ctxops,
struct vfs_cred *vcred)
{
struct gss_cli_ctx *gctx = ctx2gctx(ctx);
gctx->gc_win = 0;
atomic_set(&gctx->gc_seq, 0);
INIT_HLIST_NODE(&ctx->cc_cache);
atomic_set(&ctx->cc_refcount, 0);
ctx->cc_sec = sec;
ctx->cc_ops = ctxops;
ctx->cc_expire = 0;
ctx->cc_flags = PTLRPC_CTX_NEW;
ctx->cc_vcred = *vcred;
spin_lock_init(&ctx->cc_lock);
INIT_LIST_HEAD(&ctx->cc_req_list);
INIT_LIST_HEAD(&ctx->cc_gc_chain);
/* take a ref on belonging sec, balanced in ctx destroying */
atomic_inc(&sec->ps_refcount);
/* statistic only */
atomic_inc(&sec->ps_nctx);
CDEBUG(D_SEC, "%s@%p: create ctx %p(%u->%s)\n",
sec->ps_policy->sp_name, ctx->cc_sec,
ctx, ctx->cc_vcred.vc_uid, sec2target_str(ctx->cc_sec));
return 0;
}
/*
* return value:
* 1: the context has been taken care of by someone else
* 0: proceed to really destroy the context locally
*/
int gss_cli_ctx_fini_common(struct ptlrpc_sec *sec,
struct ptlrpc_cli_ctx *ctx)
{
struct gss_cli_ctx *gctx = ctx2gctx(ctx);
LASSERT(atomic_read(&sec->ps_nctx) > 0);
LASSERT(atomic_read(&ctx->cc_refcount) == 0);
LASSERT(ctx->cc_sec == sec);
/*
* remove UPTODATE flag of reverse ctx thus we won't send fini rpc,
* this is to avoid potential problems of client side reverse svc ctx
* be mis-destroyed in various recovery scenarios. anyway client can
* manage its reverse ctx well by associating it with its buddy ctx.
*/
if (sec_is_reverse(sec))
ctx->cc_flags &= ~PTLRPC_CTX_UPTODATE;
if (gctx->gc_mechctx) {
/* the final context fini rpc will use this ctx too, and it's
* asynchronous which finished by request_out_callback(). so
* we add refcount, whoever drop finally drop the refcount to
* 0 should responsible for the rest of destroy. */
atomic_inc(&ctx->cc_refcount);
gss_do_ctx_fini_rpc(gctx);
gss_cli_ctx_finalize(gctx);
if (!atomic_dec_and_test(&ctx->cc_refcount))
return 1;
}
if (sec_is_reverse(sec))
CWARN("reverse sec %p: destroy ctx %p\n",
ctx->cc_sec, ctx);
else
CWARN("%s@%p: destroy ctx %p(%u->%s)\n",
sec->ps_policy->sp_name, ctx->cc_sec,
ctx, ctx->cc_vcred.vc_uid, sec2target_str(ctx->cc_sec));
return 0;
}
static
int gss_alloc_reqbuf_intg(struct ptlrpc_sec *sec,
struct ptlrpc_request *req,
int svc, int msgsize)
{
int bufsize, txtsize;
int bufcnt = 2;
__u32 buflens[5];
/*
* on-wire data layout:
* - gss header
* - lustre message
* - user descriptor (optional)
* - bulk sec descriptor (optional)
* - signature (optional)
* - svc == NULL: NULL
* - svc == AUTH: signature of gss header
* - svc == INTG: signature of all above
*
* if this is context negotiation, reserver fixed space
* at the last (signature) segment regardless of svc mode.
*/
buflens[0] = PTLRPC_GSS_HEADER_SIZE;
txtsize = buflens[0];
buflens[1] = msgsize;
if (svc == SPTLRPC_SVC_INTG)
txtsize += buflens[1];
if (req->rq_pack_udesc) {
buflens[bufcnt] = sptlrpc_current_user_desc_size();
if (svc == SPTLRPC_SVC_INTG)
txtsize += buflens[bufcnt];
bufcnt++;
}
if (req->rq_pack_bulk) {
buflens[bufcnt] = gss_cli_bulk_payload(req->rq_cli_ctx,
&req->rq_flvr,
0, req->rq_bulk_read);
if (svc == SPTLRPC_SVC_INTG)
txtsize += buflens[bufcnt];
bufcnt++;
}
if (req->rq_ctx_init)
buflens[bufcnt++] = GSS_CTX_INIT_MAX_LEN;
else if (svc != SPTLRPC_SVC_NULL)
buflens[bufcnt++] = gss_cli_payload(req->rq_cli_ctx, txtsize,0);
bufsize = lustre_msg_size_v2(bufcnt, buflens);
if (!req->rq_reqbuf) {
bufsize = size_roundup_power2(bufsize);
OBD_ALLOC_LARGE(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_flvr.sf_rpc;
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 (req->rq_pack_udesc)
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)
{
__u32 ibuflens[3], wbuflens[2];
int ibufcnt;
int clearsize, wiresize;
LASSERT(req->rq_clrbuf == NULL);
LASSERT(req->rq_clrbuf_len == 0);
/* Inner (clear) buffers
* - lustre message
* - user descriptor (optional)
* - bulk checksum (optional)
*/
ibufcnt = 1;
ibuflens[0] = msgsize;
if (req->rq_pack_udesc)
ibuflens[ibufcnt++] = sptlrpc_current_user_desc_size();
if (req->rq_pack_bulk)
ibuflens[ibufcnt++] = gss_cli_bulk_payload(req->rq_cli_ctx,
&req->rq_flvr, 0,
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
* - cipher text
*/
wbuflens[0] = PTLRPC_GSS_HEADER_SIZE;
wbuflens[1] = gss_cli_payload(req->rq_cli_ctx, clearsize, 1);
wiresize = lustre_msg_size_v2(2, wbuflens);
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) {
clearsize = size_roundup_power2(clearsize);
OBD_ALLOC_LARGE(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 (req->rq_pack_udesc)
sptlrpc_pack_user_desc(req->rq_clrbuf, 1);
return 0;
}
/*
* NOTE: any change of request buffer allocation should also consider
* changing enlarge_reqbuf() series functions.
*/
int gss_alloc_reqbuf(struct ptlrpc_sec *sec,
struct ptlrpc_request *req,
int msgsize)
{
int svc = SPTLRPC_FLVR_SVC(req->rq_flvr.sf_rpc);
LASSERT(!req->rq_pack_bulk ||
(req->rq_bulk_read || req->rq_bulk_write));
switch (svc) {
case SPTLRPC_SVC_NULL:
case SPTLRPC_SVC_AUTH:
case SPTLRPC_SVC_INTG:
return gss_alloc_reqbuf_intg(sec, req, svc, msgsize);
case SPTLRPC_SVC_PRIV:
return gss_alloc_reqbuf_priv(sec, req, msgsize);
default:
LASSERTF(0, "bad rpc flavor %x\n", req->rq_flvr.sf_rpc);
return 0;
}
}
void gss_free_reqbuf(struct ptlrpc_sec *sec,
struct ptlrpc_request *req)
{
int privacy;
LASSERT(!req->rq_pool || req->rq_reqbuf);
privacy = SPTLRPC_FLVR_SVC(req->rq_flvr.sf_rpc) == SPTLRPC_SVC_PRIV;
if (!req->rq_clrbuf)
goto release_reqbuf;
/* release clear buffer */
LASSERT(privacy);
LASSERT(req->rq_clrbuf_len);
if (req->rq_pool == NULL ||
req->rq_clrbuf < req->rq_reqbuf ||
(char *) req->rq_clrbuf >=
(char *) req->rq_reqbuf + req->rq_reqbuf_len)
OBD_FREE_LARGE(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) {
LASSERT(req->rq_reqbuf_len);
OBD_FREE_LARGE(req->rq_reqbuf, req->rq_reqbuf_len);
req->rq_reqbuf = NULL;
req->rq_reqbuf_len = 0;
}
}
static int do_alloc_repbuf(struct ptlrpc_request *req, int bufsize)
{
bufsize = size_roundup_power2(bufsize);
OBD_ALLOC_LARGE(req->rq_repbuf, bufsize);
if (!req->rq_repbuf)
return -ENOMEM;
req->rq_repbuf_len = bufsize;
return 0;
}
static
int gss_alloc_repbuf_intg(struct ptlrpc_sec *sec,
struct ptlrpc_request *req,
int svc, int msgsize)
{
int txtsize;
__u32 buflens[4];
int bufcnt = 2;
int alloc_size;
/*
* on-wire data layout:
* - gss header
* - lustre message
* - bulk sec descriptor (optional)
* - signature (optional)
* - svc == NULL: NULL
* - svc == AUTH: signature of gss header
* - svc == INTG: signature of all above
*
* if this is context negotiation, reserver fixed space
* at the last (signature) segment regardless of svc mode.
*/
buflens[0] = PTLRPC_GSS_HEADER_SIZE;
txtsize = buflens[0];
buflens[1] = msgsize;
if (svc == SPTLRPC_SVC_INTG)
txtsize += buflens[1];
if (req->rq_pack_bulk) {
buflens[bufcnt] = gss_cli_bulk_payload(req->rq_cli_ctx,
&req->rq_flvr,
1, req->rq_bulk_read);
if (svc == SPTLRPC_SVC_INTG)
txtsize += buflens[bufcnt];
bufcnt++;
}
if (req->rq_ctx_init)
buflens[bufcnt++] = GSS_CTX_INIT_MAX_LEN;
else if (svc != SPTLRPC_SVC_NULL)
buflens[bufcnt++] = gss_cli_payload(req->rq_cli_ctx, txtsize,0);
alloc_size = lustre_msg_size_v2(bufcnt, buflens);
/* add space for early reply */
alloc_size += gss_at_reply_off_integ;
return do_alloc_repbuf(req, alloc_size);
}
static
int gss_alloc_repbuf_priv(struct ptlrpc_sec *sec,
struct ptlrpc_request *req,
int msgsize)
{
int txtsize;
__u32 buflens[2];
int bufcnt;
int alloc_size;
/* inner buffers */
bufcnt = 1;
buflens[0] = msgsize;
if (req->rq_pack_bulk)
buflens[bufcnt++] = gss_cli_bulk_payload(req->rq_cli_ctx,
&req->rq_flvr,
1, req->rq_bulk_read);
txtsize = lustre_msg_size_v2(bufcnt, buflens);
txtsize += GSS_MAX_CIPHER_BLOCK;
/* wrapper buffers */
bufcnt = 2;
buflens[0] = PTLRPC_GSS_HEADER_SIZE;
buflens[1] = gss_cli_payload(req->rq_cli_ctx, txtsize, 1);
alloc_size = lustre_msg_size_v2(bufcnt, buflens);
/* add space for early reply */
alloc_size += gss_at_reply_off_priv;
return do_alloc_repbuf(req, alloc_size);
}
int gss_alloc_repbuf(struct ptlrpc_sec *sec,
struct ptlrpc_request *req,
int msgsize)
{
int svc = SPTLRPC_FLVR_SVC(req->rq_flvr.sf_rpc);
LASSERT(!req->rq_pack_bulk ||
(req->rq_bulk_read || req->rq_bulk_write));
switch (svc) {
case SPTLRPC_SVC_NULL:
case SPTLRPC_SVC_AUTH:
case SPTLRPC_SVC_INTG:
return gss_alloc_repbuf_intg(sec, req, svc, msgsize);
case SPTLRPC_SVC_PRIV:
return gss_alloc_repbuf_priv(sec, req, msgsize);
default:
LASSERTF(0, "bad rpc flavor %x\n", req->rq_flvr.sf_rpc);
return 0;
}
}
void gss_free_repbuf(struct ptlrpc_sec *sec,
struct ptlrpc_request *req)
{
OBD_FREE_LARGE(req->rq_repbuf, req->rq_repbuf_len);
req->rq_repbuf = NULL;
req->rq_repbuf_len = 0;
req->rq_repdata = NULL;
req->rq_repdata_len = 0;
}
static int get_enlarged_msgsize(struct lustre_msg *msg,
int segment, int newsize)
{
int save, newmsg_size;
LASSERT(newsize >= msg->lm_buflens[segment]);
save = msg->lm_buflens[segment];
msg->lm_buflens[segment] = newsize;
newmsg_size = lustre_msg_size_v2(msg->lm_bufcount, msg->lm_buflens);
msg->lm_buflens[segment] = save;
return newmsg_size;
}
static int get_enlarged_msgsize2(struct lustre_msg *msg,
int segment1, int newsize1,
int segment2, int newsize2)
{
int save1, save2, newmsg_size;
LASSERT(newsize1 >= msg->lm_buflens[segment1]);
LASSERT(newsize2 >= msg->lm_buflens[segment2]);
save1 = msg->lm_buflens[segment1];
save2 = msg->lm_buflens[segment2];
msg->lm_buflens[segment1] = newsize1;
msg->lm_buflens[segment2] = newsize2;
newmsg_size = lustre_msg_size_v2(msg->lm_bufcount, msg->lm_buflens);
msg->lm_buflens[segment1] = save1;
msg->lm_buflens[segment2] = save2;
return newmsg_size;
}
static
int gss_enlarge_reqbuf_intg(struct ptlrpc_sec *sec,
struct ptlrpc_request *req,
int svc,
int segment, int newsize)
{
struct lustre_msg *newbuf;
int txtsize, sigsize = 0, i;
int newmsg_size, newbuf_size;
/*
* gss header is at seg 0;
* embedded msg is at seg 1;
* signature (if any) is at the last seg
*/
LASSERT(req->rq_reqbuf);
LASSERT(req->rq_reqbuf_len > req->rq_reqlen);
LASSERT(req->rq_reqbuf->lm_bufcount >= 2);
LASSERT(lustre_msg_buf(req->rq_reqbuf, 1, 0) == req->rq_reqmsg);
/* 1. compute new embedded msg size */
newmsg_size = get_enlarged_msgsize(req->rq_reqmsg, segment, newsize);
LASSERT(newmsg_size >= req->rq_reqbuf->lm_buflens[1]);
/* 2. compute new wrapper msg size */
if (svc == SPTLRPC_SVC_NULL) {
/* no signature, get size directly */
newbuf_size = get_enlarged_msgsize(req->rq_reqbuf,
1, newmsg_size);
} else {
txtsize = req->rq_reqbuf->lm_buflens[0];
if (svc == SPTLRPC_SVC_INTG) {
for (i = 1; i < req->rq_reqbuf->lm_bufcount; i++)
txtsize += req->rq_reqbuf->lm_buflens[i];
txtsize += newmsg_size - req->rq_reqbuf->lm_buflens[1];
}
sigsize = gss_cli_payload(req->rq_cli_ctx, txtsize, 0);
LASSERT(sigsize >= msg_last_seglen(req->rq_reqbuf));
newbuf_size = get_enlarged_msgsize2(
req->rq_reqbuf,
1, newmsg_size,
msg_last_segidx(req->rq_reqbuf),
sigsize);
}
/* request from pool should always have enough buffer */
LASSERT(!req->rq_pool || req->rq_reqbuf_len >= newbuf_size);
if (req->rq_reqbuf_len < newbuf_size) {
newbuf_size = size_roundup_power2(newbuf_size);
OBD_ALLOC_LARGE(newbuf, newbuf_size);
if (newbuf == NULL)
return -ENOMEM;
/* Must lock this, so that otherwise unprotected change of
* rq_reqmsg is not racing with parallel processing of
* imp_replay_list traversing threads. See LU-3333
* This is a bandaid at best, we really need to deal with this
* in request enlarging code before unpacking that's already
* there */
if (req->rq_import)
spin_lock(&req->rq_import->imp_lock);
memcpy(newbuf, req->rq_reqbuf, req->rq_reqbuf_len);
OBD_FREE_LARGE(req->rq_reqbuf, req->rq_reqbuf_len);
req->rq_reqbuf = newbuf;
req->rq_reqbuf_len = newbuf_size;
req->rq_reqmsg = lustre_msg_buf(req->rq_reqbuf, 1, 0);
if (req->rq_import)
spin_unlock(&req->rq_import->imp_lock);
}
/* do enlargement, from wrapper to embedded, from end to begin */
if (svc != SPTLRPC_SVC_NULL)
_sptlrpc_enlarge_msg_inplace(req->rq_reqbuf,
msg_last_segidx(req->rq_reqbuf),
sigsize);
_sptlrpc_enlarge_msg_inplace(req->rq_reqbuf, 1, newmsg_size);
_sptlrpc_enlarge_msg_inplace(req->rq_reqmsg, segment, newsize);
req->rq_reqlen = newmsg_size;
return 0;
}
static
int gss_enlarge_reqbuf_priv(struct ptlrpc_sec *sec,
struct ptlrpc_request *req,
int segment, int newsize)
{
struct lustre_msg *newclrbuf;
int newmsg_size, newclrbuf_size, newcipbuf_size;
__u32 buflens[3];
/*
* embedded msg is at seg 0 of clear buffer;
* cipher text is at seg 2 of cipher buffer;
*/
LASSERT(req->rq_pool ||
(req->rq_reqbuf == NULL && req->rq_reqbuf_len == 0));
LASSERT(req->rq_reqbuf == NULL ||
(req->rq_pool && req->rq_reqbuf->lm_bufcount == 3));
LASSERT(req->rq_clrbuf);
LASSERT(req->rq_clrbuf_len > req->rq_reqlen);
LASSERT(lustre_msg_buf(req->rq_clrbuf, 0, 0) == req->rq_reqmsg);
/* compute new embedded msg size */
newmsg_size = get_enlarged_msgsize(req->rq_reqmsg, segment, newsize);
/* compute new clear buffer size */
newclrbuf_size = get_enlarged_msgsize(req->rq_clrbuf, 0, newmsg_size);
newclrbuf_size += GSS_MAX_CIPHER_BLOCK;
/* compute new cipher buffer size */
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, newclrbuf_size, 1);
newcipbuf_size = lustre_msg_size_v2(3, buflens);
/* handle the case that we put both clear buf and cipher buf into
* pre-allocated single buffer. */
if (unlikely(req->rq_pool) &&
req->rq_clrbuf >= req->rq_reqbuf &&
(char *) req->rq_clrbuf <
(char *) req->rq_reqbuf + req->rq_reqbuf_len) {
/* it couldn't be better we still fit into the
* pre-allocated buffer. */
if (newclrbuf_size + newcipbuf_size <= req->rq_reqbuf_len) {
void *src, *dst;
if (req->rq_import)
spin_lock(&req->rq_import->imp_lock);
/* move clear text backward. */
src = req->rq_clrbuf;
dst = (char *) req->rq_reqbuf + newcipbuf_size;
memmove(dst, src, req->rq_clrbuf_len);
req->rq_clrbuf = (struct lustre_msg *) dst;
req->rq_clrbuf_len = newclrbuf_size;
req->rq_reqmsg = lustre_msg_buf(req->rq_clrbuf, 0, 0);
if (req->rq_import)
spin_unlock(&req->rq_import->imp_lock);
} else {
/* sadly we have to split out the clear buffer */
LASSERT(req->rq_reqbuf_len >= newcipbuf_size);
LASSERT(req->rq_clrbuf_len < newclrbuf_size);
}
}
if (req->rq_clrbuf_len < newclrbuf_size) {
newclrbuf_size = size_roundup_power2(newclrbuf_size);
OBD_ALLOC_LARGE(newclrbuf, newclrbuf_size);
if (newclrbuf == NULL)
return -ENOMEM;
/* Must lock this, so that otherwise unprotected change of
* rq_reqmsg is not racing with parallel processing of
* imp_replay_list traversing threads. See LU-3333
* This is a bandaid at best, we really need to deal with this
* in request enlarging code before unpacking that's already
* there */
if (req->rq_import)
spin_lock(&req->rq_import->imp_lock);
memcpy(newclrbuf, req->rq_clrbuf, req->rq_clrbuf_len);
if (req->rq_reqbuf == NULL ||
req->rq_clrbuf < req->rq_reqbuf ||
(char *) req->rq_clrbuf >=
(char *) req->rq_reqbuf + req->rq_reqbuf_len) {
OBD_FREE_LARGE(req->rq_clrbuf, req->rq_clrbuf_len);
}
req->rq_clrbuf = newclrbuf;
req->rq_clrbuf_len = newclrbuf_size;
req->rq_reqmsg = lustre_msg_buf(req->rq_clrbuf, 0, 0);
if (req->rq_import)
spin_unlock(&req->rq_import->imp_lock);
}
_sptlrpc_enlarge_msg_inplace(req->rq_clrbuf, 0, newmsg_size);
_sptlrpc_enlarge_msg_inplace(req->rq_reqmsg, segment, newsize);
req->rq_reqlen = newmsg_size;
return 0;
}
int gss_enlarge_reqbuf(struct ptlrpc_sec *sec,
struct ptlrpc_request *req,
int segment, int newsize)
{
int svc = SPTLRPC_FLVR_SVC(req->rq_flvr.sf_rpc);
LASSERT(!req->rq_ctx_init && !req->rq_ctx_fini);
switch (svc) {
case SPTLRPC_SVC_NULL:
case SPTLRPC_SVC_AUTH:
case SPTLRPC_SVC_INTG:
return gss_enlarge_reqbuf_intg(sec, req, svc, segment, newsize);
case SPTLRPC_SVC_PRIV:
return gss_enlarge_reqbuf_priv(sec, req, segment, newsize);
default:
LASSERTF(0, "bad rpc flavor %x\n", req->rq_flvr.sf_rpc);
return 0;
}
}
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;
}
/********************************************
* 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_PTR(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,
__u32 svc)
{
__u32 flags = 0;
int rc;
LASSERT(rs->rs_msg == lustre_msg_buf(rs->rs_repbuf, 1, 0));
/* embedded lustre_msg might have been shrunk */
if (req->rq_replen != rs->rs_repbuf->lm_buflens[1])
lustre_shrink_msg(rs->rs_repbuf, 1, req->rq_replen, 1);
if (req->rq_pack_bulk)
flags |= LUSTRE_GSS_PACK_BULK;
rc = gss_sign_msg(rs->rs_repbuf, grctx->src_ctx->gsc_mechctx,
LUSTRE_SP_ANY, flags, PTLRPC_GSS_PROC_DATA,
grctx->src_wirectx.gw_seq, svc, NULL);
if (rc < 0)
return rc;
rs->rs_repdata_len = rc;
if (likely(req->rq_packed_final)) {
if (lustre_msghdr_get_flags(req->rq_reqmsg) & MSGHDR_AT_SUPPORT)
req->rq_reply_off = gss_at_reply_off_integ;
else
req->rq_reply_off = 0;
} else {
if (svc == SPTLRPC_SVC_NULL)
rs->rs_repbuf->lm_cksum = crc32_le(!(__u32) 0,
lustre_msg_buf(rs->rs_repbuf, 1, 0),
lustre_msg_buflen(rs->rs_repbuf, 1));
req->rq_reply_off = 0;
}
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;
//if (OBD_FAIL_CHECK_ORSET(OBD_FAIL_SVCGSS_ERR_NOTIFY, OBD_FAIL_ONCE))
// return -EINVAL;
grctx->src_err_notify = 1;
grctx->src_reserve_len = 0;
rc = lustre_pack_reply_v2(req, 1, &replen, NULL, 0);
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 swabbed, rc;
CDEBUG(D_SEC, "processing gss init(%d) request from %s\n", gw->gw_proc,
libcfs_nid2str(req->rq_peer.nid));
req->rq_ctx_init = 1;
if (gw->gw_flags & LUSTRE_GSS_PACK_BULK) {
CERROR("unexpected bulk flag\n");
return SECSVC_DROP;
}
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;
}
swabbed = ptlrpc_req_need_swab(req);
/* 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)\n",
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 (grctx->src_ctx->gsc_usr_mds || grctx->src_ctx->gsc_usr_oss ||
grctx->src_ctx->gsc_usr_root)
CWARN("create svc ctx %p: user from %s authenticated as %s\n",
grctx->src_ctx, libcfs_nid2str(req->rq_peer.nid),
grctx->src_ctx->gsc_usr_mds ? "mds" :
(grctx->src_ctx->gsc_usr_oss ? "oss" : "root"));
else
CWARN("create svc ctx %p: accept user %u from %s\n",
grctx->src_ctx, grctx->src_ctx->gsc_uid,
libcfs_nid2str(req->rq_peer.nid));
if (gw->gw_flags & LUSTRE_GSS_PACK_USER) {
if (reqbuf->lm_bufcount < 4) {
CERROR("missing user descriptor\n");
return SECSVC_DROP;
}
if (sptlrpc_unpack_user_desc(reqbuf, 2, swabbed)) {
CERROR("Mal-formed user descriptor\n");
return SECSVC_DROP;
}
req->rq_pack_udesc = 1;
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_reqctx *grctx,
struct gss_wire_ctx *gw,
__u32 *major)
{
struct gss_svc_ctx *gctx = grctx->src_ctx;
struct lustre_msg *msg = req->rq_reqbuf;
int offset = 2;
int swabbed;
*major = GSS_S_COMPLETE;
if (msg->lm_bufcount < 2) {
CERROR("Too few segments (%u) in request\n", msg->lm_bufcount);
return -EINVAL;
}
if (gw->gw_svc == SPTLRPC_SVC_NULL)
goto verified;
if (gss_check_seq_num(&gctx->gsc_seqdata, gw->gw_seq, 0)) {
CERROR("phase 0: discard replayed req: seq %u\n", gw->gw_seq);
*major = GSS_S_DUPLICATE_TOKEN;
return -EACCES;
}
*major = gss_verify_msg(msg, gctx->gsc_mechctx, gw->gw_svc);
if (*major != GSS_S_COMPLETE) {
CERROR("failed to verify request: %x\n", *major);
return -EACCES;
}
if (gctx->gsc_reverse == 0 &&
gss_check_seq_num(&gctx->gsc_seqdata, gw->gw_seq, 1)) {
CERROR("phase 1+: discard replayed req: seq %u\n", gw->gw_seq);
*major = GSS_S_DUPLICATE_TOKEN;
return -EACCES;
}
verified:
swabbed = ptlrpc_req_need_swab(req);
/* user descriptor */
if (gw->gw_flags & LUSTRE_GSS_PACK_USER) {
if (msg->lm_bufcount < (offset + 1)) {
CERROR("no user desc included\n");
return -EINVAL;
}
if (sptlrpc_unpack_user_desc(msg, offset, swabbed)) {
CERROR("Mal-formed user descriptor\n");
return -EINVAL;
}
req->rq_pack_udesc = 1;
req->rq_user_desc = lustre_msg_buf(msg, offset, 0);
offset++;
}
/* check bulk_sec_desc data */
if (gw->gw_flags & LUSTRE_GSS_PACK_BULK) {
if (msg->lm_bufcount < (offset + 1)) {
CERROR("missing bulk sec descriptor\n");
return -EINVAL;
}
if (bulk_sec_desc_unpack(msg, offset, swabbed))
return -EINVAL;
req->rq_pack_bulk = 1;
grctx->src_reqbsd = lustre_msg_buf(msg, offset, 0);
grctx->src_reqbsd_size = lustre_msg_buflen(msg, offset);
}
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_reqctx *grctx,
struct gss_wire_ctx *gw,
__u32 *major)
{
struct gss_svc_ctx *gctx = grctx->src_ctx;
struct lustre_msg *msg = req->rq_reqbuf;
int swabbed, msglen, offset = 1;
if (gss_check_seq_num(&gctx->gsc_seqdata, gw->gw_seq, 0)) {
CERROR("phase 0: 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) {
CERROR("failed to unwrap request: %x\n", *major);
return -EACCES;
}
if (gss_check_seq_num(&gctx->gsc_seqdata, gw->gw_seq, 1)) {
CERROR("phase 1+: discard replayed req: seq %u\n", gw->gw_seq);
*major = GSS_S_DUPLICATE_TOKEN;
return -EACCES;
}
swabbed = __lustre_unpack_msg(msg, msglen);
if (swabbed < 0) {
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 (gw->gw_flags & LUSTRE_GSS_PACK_USER) {
if (msg->lm_bufcount < offset + 1) {
CERROR("no user descriptor included\n");
return -EINVAL;
}
if (sptlrpc_unpack_user_desc(msg, offset, swabbed)) {
CERROR("Mal-formed user descriptor\n");
return -EINVAL;
}
req->rq_pack_udesc = 1;
req->rq_user_desc = lustre_msg_buf(msg, offset, 0);
offset++;
}
if (gw->gw_flags & LUSTRE_GSS_PACK_BULK) {
if (msg->lm_bufcount < offset + 1) {
CERROR("no bulk checksum included\n");
return -EINVAL;
}
if (bulk_sec_desc_unpack(msg, offset, swabbed))
return -EINVAL;
req->rq_pack_bulk = 1;
grctx->src_reqbsd = lustre_msg_buf(msg, offset, 0);
grctx->src_reqbsd_size = lustre_msg_buflen(msg, offset);
}
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;
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 SPTLRPC_SVC_NULL:
case SPTLRPC_SVC_AUTH:
case SPTLRPC_SVC_INTG:
rc = gss_svc_verify_request(req, grctx, gw, &major);
break;
case SPTLRPC_SVC_PRIV:
rc = gss_svc_unseal_request(req, grctx, gw, &major);
break;
default:
CERROR("unsupported gss service %d\n", gw->gw_svc);
rc = -EINVAL;
}
if (rc == 0)
return SECSVC_OK;
CERROR("svc %u failed: major 0x%08x: req xid "LPU64" ctx %p idx "
LPX64"(%u->%s)\n", gw->gw_svc, major, req->rq_xid,
grctx->src_ctx, gss_handle_to_u64(&gw->gw_handle),
grctx->src_ctx->gsc_uid, libcfs_nid2str(req->rq_peer.nid));
error:
/* 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);
__u32 major;
req->rq_ctx_fini = 1;
req->rq_no_reply = 1;
grctx->src_ctx = gss_svc_upcall_get_ctx(req, gw);
if (!grctx->src_ctx) {
CDEBUG(D_SEC, "invalid gss context handle for destroy.\n");
return SECSVC_DROP;
}
if (gw->gw_svc != SPTLRPC_SVC_INTG) {
CERROR("svc %u is not supported in destroy.\n", gw->gw_svc);
return SECSVC_DROP;
}
if (gss_svc_verify_request(req, grctx, gw, &major))
return SECSVC_DROP;
CWARN("destroy svc ctx %p idx "LPX64" (%u->%s)\n",
grctx->src_ctx, gss_handle_to_u64(&gw->gw_handle),
grctx->src_ctx->gsc_uid, libcfs_nid2str(req->rq_peer.nid));
gss_svc_upcall_destroy_ctx(grctx->src_ctx);
if (gw->gw_flags & LUSTRE_GSS_PACK_USER) {
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,
ptlrpc_req_need_swab(req))) {
CERROR("Mal-formed user descriptor, ignore it\n");
return SECSVC_OK;
}
req->rq_pack_udesc = 1;
req->rq_user_desc = lustre_msg_buf(req->rq_reqbuf, 2, 0);
}
return SECSVC_OK;
}
int gss_svc_accept(struct ptlrpc_sec_policy *policy, struct ptlrpc_request *req)
{
struct gss_header *ghdr;
struct gss_svc_reqctx *grctx;
struct gss_wire_ctx *gw;
int swabbed, rc;
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;
}
swabbed = ptlrpc_req_need_swab(req);
ghdr = gss_swab_header(req->rq_reqbuf, 0, swabbed);
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;
}
req->rq_sp_from = ghdr->gh_sp;
/* alloc grctx data */
OBD_ALLOC_PTR(grctx);
if (!grctx)
return SECSVC_DROP;
grctx->src_base.sc_policy = sptlrpc_policy_get(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_flags = ghdr->gh_flags;
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 (swabbed)
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_mdt = grctx->src_ctx->gsc_usr_mds;
req->rq_auth_usr_ost = grctx->src_ctx->gsc_usr_oss;
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;
}
void gss_svc_invalidate_ctx(struct ptlrpc_svc_ctx *svc_ctx)
{
struct gss_svc_reqctx *grctx;
if (svc_ctx == NULL) {
return;
}
grctx = gss_svc_ctx2reqctx(svc_ctx);
CWARN("gss svc invalidate ctx %p(%u)\n",
grctx->src_ctx, grctx->src_ctx->gsc_uid);
gss_svc_upcall_destroy_ctx(grctx->src_ctx);
}
static inline
int gss_svc_payload(struct gss_svc_reqctx *grctx, int early,
int msgsize, int privacy)
{
/* we should treat early reply normally, but which is actually sharing
* the same ctx with original request, so in this case we should
* ignore the special ctx's special flags */
if (early == 0 && gss_svc_reqctx_is_special(grctx))
return grctx->src_reserve_len;
return gss_mech_payload(NULL, msgsize, privacy);
}
static int gss_svc_bulk_payload(struct gss_svc_ctx *gctx,
struct sptlrpc_flavor *flvr,
int read)
{
int payload = sizeof(struct ptlrpc_bulk_sec_desc);
if (read) {
switch (SPTLRPC_FLVR_BULK_SVC(flvr->sf_rpc)) {
case SPTLRPC_BULK_SVC_NULL:
break;
case SPTLRPC_BULK_SVC_INTG:
payload += gss_mech_payload(NULL, 0, 0);
break;
case SPTLRPC_BULK_SVC_PRIV:
payload += gss_mech_payload(NULL, 0, 1);
break;
case SPTLRPC_BULK_SVC_AUTH:
default:
LBUG();
}
}
return payload;
}
int gss_svc_alloc_rs(struct ptlrpc_request *req, int msglen)
{
struct gss_svc_reqctx *grctx;
struct ptlrpc_reply_state *rs;
int early, privacy, svc, bsd_off = 0;
__u32 ibuflens[2], buflens[4];
int ibufcnt = 0, bufcnt;
int txtsize, wmsg_size, rs_size;
LASSERT(msglen % 8 == 0);
if (req->rq_pack_bulk && !req->rq_bulk_read && !req->rq_bulk_write) {
CERROR("client request bulk sec on non-bulk rpc\n");
return -EPROTO;
}
svc = SPTLRPC_FLVR_SVC(req->rq_flvr.sf_rpc);
early = (req->rq_packed_final == 0);
grctx = gss_svc_ctx2reqctx(req->rq_svc_ctx);
if (!early && gss_svc_reqctx_is_special(grctx))
privacy = 0;
else
privacy = (svc == SPTLRPC_SVC_PRIV);
if (privacy) {
/* inner clear buffers */
ibufcnt = 1;
ibuflens[0] = msglen;
if (req->rq_pack_bulk) {
LASSERT(grctx->src_reqbsd);
bsd_off = ibufcnt;
ibuflens[ibufcnt++] = gss_svc_bulk_payload(
grctx->src_ctx,
&req->rq_flvr,
req->rq_bulk_read);
}
txtsize = lustre_msg_size_v2(ibufcnt, ibuflens);
txtsize += GSS_MAX_CIPHER_BLOCK;
/* wrapper buffer */
bufcnt = 2;
buflens[0] = PTLRPC_GSS_HEADER_SIZE;
buflens[1] = gss_svc_payload(grctx, early, txtsize, 1);
} else {
bufcnt = 2;
buflens[0] = PTLRPC_GSS_HEADER_SIZE;
buflens[1] = msglen;
txtsize = buflens[0];
if (svc == SPTLRPC_SVC_INTG)
txtsize += buflens[1];
if (req->rq_pack_bulk) {
LASSERT(grctx->src_reqbsd);
bsd_off = bufcnt;
buflens[bufcnt] = gss_svc_bulk_payload(
grctx->src_ctx,
&req->rq_flvr,
req->rq_bulk_read);
if (svc == SPTLRPC_SVC_INTG)
txtsize += buflens[bufcnt];
bufcnt++;
}
if ((!early && gss_svc_reqctx_is_special(grctx)) ||
svc != SPTLRPC_SVC_NULL)
buflens[bufcnt++] = gss_svc_payload(grctx, early,
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_LARGE(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;
/* initialize the buffer */
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_flvr.sf_rpc;
rs->rs_msg = lustre_msg_buf(rs->rs_repbuf, 1, 0);
}
if (bsd_off) {
grctx->src_repbsd = lustre_msg_buf(rs->rs_repbuf, bsd_off, 0);
grctx->src_repbsd_size = lustre_msg_buflen(rs->rs_repbuf,
bsd_off);
}
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 hdrobj, msgobj, token;
struct gss_header *ghdr;
__u8 *token_buf;
int token_buflen;
__u32 buflens[2], major;
int msglen, rc;
/* get clear data length. note embedded lustre_msg might
* have been shrunk */
if (req->rq_replen != lustre_msg_buflen(rs->rs_repbuf, 0))
msglen = lustre_shrink_msg(rs->rs_repbuf, 0, req->rq_replen, 1);
else
msglen = lustre_msg_size_v2(rs->rs_repbuf->lm_bufcount,
rs->rs_repbuf->lm_buflens);
/* temporarily use tail of buffer to hold gss header data */
LASSERT(msglen + PTLRPC_GSS_HEADER_SIZE <= rs->rs_repbuf_len);
ghdr = (struct gss_header *) ((char *) rs->rs_repbuf +
rs->rs_repbuf_len - PTLRPC_GSS_HEADER_SIZE);
ghdr->gh_version = PTLRPC_GSS_VERSION;
ghdr->gh_sp = LUSTRE_SP_ANY;
ghdr->gh_flags = 0;
ghdr->gh_proc = PTLRPC_GSS_PROC_DATA;
ghdr->gh_seq = grctx->src_wirectx.gw_seq;
ghdr->gh_svc = SPTLRPC_SVC_PRIV;
ghdr->gh_handle.len = 0;
if (req->rq_pack_bulk)
ghdr->gh_flags |= LUSTRE_GSS_PACK_BULK;
/* allocate temporary cipher buffer */
token_buflen = gss_mech_payload(gctx->gsc_mechctx, msglen, 1);
OBD_ALLOC_LARGE(token_buf, token_buflen);
if (token_buf == NULL)
return -ENOMEM;
hdrobj.len = PTLRPC_GSS_HEADER_SIZE;
hdrobj.data = (__u8 *) ghdr;
msgobj.len = msglen;
msgobj.data = (__u8 *) rs->rs_repbuf;
token.len = token_buflen;
token.data = token_buf;
major = lgss_wrap(gctx->gsc_mechctx, &hdrobj, &msgobj,
rs->rs_repbuf_len - PTLRPC_GSS_HEADER_SIZE, &token);
if (major != GSS_S_COMPLETE) {
CERROR("wrap message error: %08x\n", major);
GOTO(out_free, rc = -EPERM);
}
LASSERT(token.len <= token_buflen);
/* we are about to override data at rs->rs_repbuf, nullify pointers
* to which to catch further illegal usage. */
if (req->rq_pack_bulk) {
grctx->src_repbsd = NULL;
grctx->src_repbsd_size = 0;
}
/* now fill the actual wire data
* - gss header
* - gss token
*/
buflens[0] = PTLRPC_GSS_HEADER_SIZE;
buflens[1] = token.len;
rs->rs_repdata_len = lustre_msg_size_v2(2, buflens);
LASSERT(rs->rs_repdata_len <= rs->rs_repbuf_len);
lustre_init_msg_v2(rs->rs_repbuf, 2, buflens, NULL);
rs->rs_repbuf->lm_secflvr = req->rq_flvr.sf_rpc;
memcpy(lustre_msg_buf(rs->rs_repbuf, 0, 0), ghdr,
PTLRPC_GSS_HEADER_SIZE);
memcpy(lustre_msg_buf(rs->rs_repbuf, 1, 0), token.data, token.len);
/* reply offset */
if (req->rq_packed_final &&
(lustre_msghdr_get_flags(req->rq_reqmsg) & MSGHDR_AT_SUPPORT))
req->rq_reply_off = gss_at_reply_off_priv;
else
req->rq_reply_off = 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_LARGE(token_buf, token_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 = &grctx->src_wirectx;
int early, rc;
early = (req->rq_packed_final == 0);
if (!early && gss_svc_reqctx_is_special(grctx)) {
LASSERT(rs->rs_repdata_len != 0);
req->rq_reply_off = gss_at_reply_off_integ;
return 0;
}
/* early reply could happen in many cases */
if (!early &&
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 SPTLRPC_SVC_NULL:
case SPTLRPC_SVC_AUTH:
case SPTLRPC_SVC_INTG:
rc = gss_svc_sign(req, rs, grctx, gw->gw_svc);
break;
case SPTLRPC_SVC_PRIV:
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;
}
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_LARGE(rs, rs->rs_size);
}
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));
}
int gss_copy_rvc_cli_ctx(struct ptlrpc_cli_ctx *cli_ctx,
struct ptlrpc_svc_ctx *svc_ctx)
{
struct gss_cli_ctx *cli_gctx = ctx2gctx(cli_ctx);
struct gss_svc_ctx *svc_gctx = gss_svc_ctx2gssctx(svc_ctx);
struct gss_ctx *mechctx = NULL;
LASSERT(cli_gctx);
LASSERT(svc_gctx && svc_gctx->gsc_mechctx);
cli_gctx->gc_proc = PTLRPC_GSS_PROC_DATA;
cli_gctx->gc_win = GSS_SEQ_WIN;
/* The problem is the reverse ctx might get lost in some recovery
* situations, and the same svc_ctx will be used to re-create it.
* if there's callback be sentout before that, new reverse ctx start
* with sequence 0 will lead to future callback rpc be treated as
* replay.
*
* each reverse root ctx will record its latest sequence number on its
* buddy svcctx before be destroyed, so here we continue use it.
*/
atomic_set(&cli_gctx->gc_seq, svc_gctx->gsc_rvs_seq);
if (gss_svc_upcall_dup_handle(&cli_gctx->gc_svc_handle, svc_gctx)) {
CERROR("failed to dup svc handle\n");
goto err_out;
}
if (lgss_copy_reverse_context(svc_gctx->gsc_mechctx, &mechctx) !=
GSS_S_COMPLETE) {
CERROR("failed to copy mech context\n");
goto err_svc_handle;
}
if (rawobj_dup(&cli_gctx->gc_handle, &svc_gctx->gsc_rvs_hdl)) {
CERROR("failed to dup reverse handle\n");
goto err_ctx;
}
cli_gctx->gc_mechctx = mechctx;
gss_cli_ctx_uptodate(cli_gctx);
return 0;
err_ctx:
lgss_delete_sec_context(&mechctx);
err_svc_handle:
rawobj_free(&cli_gctx->gc_svc_handle);
err_out:
return -ENOMEM;
}
static void gss_init_at_reply_offset(void)
{
__u32 buflens[3];
int clearsize;
buflens[0] = PTLRPC_GSS_HEADER_SIZE;
buflens[1] = lustre_msg_early_size();
buflens[2] = gss_cli_payload(NULL, buflens[1], 0);
gss_at_reply_off_integ = lustre_msg_size_v2(3, buflens);
buflens[0] = lustre_msg_early_size();
clearsize = lustre_msg_size_v2(1, buflens);
buflens[0] = PTLRPC_GSS_HEADER_SIZE;
buflens[1] = gss_cli_payload(NULL, clearsize, 0);
buflens[2] = gss_cli_payload(NULL, clearsize, 1);
gss_at_reply_off_priv = lustre_msg_size_v2(3, buflens);
}
int __init sptlrpc_gss_init(void)
{
int rc;
rc = gss_init_lproc();
if (rc)
return rc;
rc = gss_init_cli_upcall();
if (rc)
goto out_lproc;
rc = gss_init_svc_upcall();
if (rc)
goto out_cli_upcall;
rc = init_kerberos_module();
if (rc)
goto out_svc_upcall;
/* register policy after all other stuff be initialized, because it
* might be in used immediately after the registration. */
rc = gss_init_keyring();
if (rc)
goto out_kerberos;
#ifdef HAVE_GSS_PIPEFS
rc = gss_init_pipefs();
if (rc)
goto out_keyring;
#endif
gss_init_at_reply_offset();
return 0;
#ifdef HAVE_GSS_PIPEFS
out_keyring:
gss_exit_keyring();
#endif
out_kerberos:
cleanup_kerberos_module();
out_svc_upcall:
gss_exit_svc_upcall();
out_cli_upcall:
gss_exit_cli_upcall();
out_lproc:
gss_exit_lproc();
return rc;
}
static void __exit sptlrpc_gss_exit(void)
{
gss_exit_keyring();
#ifdef HAVE_GSS_PIPEFS
gss_exit_pipefs();
#endif
cleanup_kerberos_module();
gss_exit_svc_upcall();
gss_exit_cli_upcall();
gss_exit_lproc();
}
MODULE_AUTHOR("Sun Microsystems, Inc. <http://www.lustre.org/>");
MODULE_DESCRIPTION("GSS security policy for Lustre");
MODULE_LICENSE("GPL");
module_init(sptlrpc_gss_init);
module_exit(sptlrpc_gss_exit);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment