RPC: Support the KEYRING model for AUTH_GSS credentials Signed-off-by: Trond Myklebust --- fs/Kconfig | 14 + include/linux/sunrpc/auth_gss.h | 2 net/sunrpc/auth_gss/auth_gss.c | 313 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 314 insertions(+), 15 deletions(-) Index: linux-2.6.13-rc1/net/sunrpc/auth_gss/auth_gss.c =================================================================== --- linux-2.6.13-rc1.orig/net/sunrpc/auth_gss/auth_gss.c +++ linux-2.6.13-rc1/net/sunrpc/auth_gss/auth_gss.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,18 @@ #include #include +#ifdef CONFIG_RPCSEC_GSS_KEYRING + +#include + +#else + +#define gss_key_lookup_cred(auth) ERR_PTR(-ENOKEY) +#define gss_register_keytype() (0) +#define gss_unregister_keytype() do { } while(0) + +#endif + static struct rpc_authops authgss_ops; static struct rpc_credops gss_credops; @@ -83,6 +96,7 @@ static struct rpc_credops gss_credops; static DEFINE_RWLOCK(gss_ctx_lock); struct gss_auth { + spinlock_t lock; struct rpc_auth rpc_auth; struct gss_api_mech *mech; enum rpc_gss_svc service; @@ -90,7 +104,7 @@ struct gss_auth { struct rpc_clnt *client; struct dentry *dentry; char path[48]; - spinlock_t lock; + char key_name[256]; }; static void gss_destroy_ctx(struct gss_cl_ctx *); @@ -696,6 +710,13 @@ gss_create(struct rpc_clnt *clnt, rpc_au if (err) goto err_put_mech; + snprintf(gss_auth->key_name, sizeof(gss_auth->key_name), + "mechanism=\"%s\" service=\"%s%u\" host=\"%s\"", + gss_auth->mech->gm_pfs[gss_auth->service-RPC_GSS_SVC_NONE].name, + clnt->cl_protname, + clnt->cl_vers, + clnt->cl_server); + snprintf(gss_auth->path, sizeof(gss_auth->path), "%s/%s", clnt->cl_pathname, gss_auth->mech->gm_name); @@ -747,6 +768,23 @@ gss_destroy_ctx(struct gss_cl_ctx *ctx) kfree(ctx); } +static inline struct gss_cred * +gss_alloc_cred(struct gss_auth *gss_auth) +{ + struct gss_cred *cred; + + dprintk("RPC: gss_alloc_cred \n"); + + cred = kmalloc(sizeof(*cred), GFP_KERNEL); + if (cred != NULL) { + memset(cred, 0, sizeof(*cred)); + atomic_set(&cred->gc_count, 1); + cred->gc_base.cr_ops = &gss_credops; + cred->gc_service = gss_auth->service; + } + return cred; +} + static void gss_destroy_cred(struct rpc_cred *rc) { @@ -756,16 +794,266 @@ gss_destroy_cred(struct rpc_cred *rc) if (cred->gc_ctx) gss_put_ctx(cred->gc_ctx); + if (cred->gc_key) + key_put(cred->gc_key); kfree(cred); } +#ifdef CONFIG_RPCSEC_GSS_KEYRING +static inline const void * +simple_skip_bytes(const void *p, const void *end, size_t len) +{ + const void *q = (const void *)((const char *)p + len); + if (unlikely(q > end || q < p)) + return ERR_PTR(-EFAULT); + return q; +} + + +static struct gss_cl_ctx * +gss_key_read_context(const void *p, const void *end) +{ + struct gss_cl_ctx *ctx; + struct gss_api_mech *mech; + size_t len, maxlen; + + /* First up should be the name of the mechanism */ + maxlen = end - p; + len = strnlen((const char *)p, maxlen); + if (len == maxlen) + return ERR_PTR(-EFAULT); + /* find the mechanism */ + mech = gss_mech_get_by_name((const char *)p); + if (mech == NULL) + return ERR_PTR(-EINVAL); + p = simple_skip_bytes(p, end, len + 1); + if (IS_ERR(p)) + goto err_put_mech; + /* Next we want the name of the principal */ + maxlen = end - p; + len = strnlen((const char *)p, maxlen); + if (len == maxlen) { + p = ERR_PTR(-EFAULT); + goto err_put_mech; + } + ctx = gss_alloc_context((const char *)p); + p = simple_skip_bytes(p, end, len + 1); + if (IS_ERR(p)) + goto err_free_ctx; + /* Now read in context */ + p = gss_fill_context(p, end, ctx, mech); + if (IS_ERR(p)) + goto err_free_ctx; + return ctx; +err_free_ctx: + kfree(ctx); +err_put_mech: + gss_mech_put(mech); + return (struct gss_cl_ctx *)p; +} + +static int +gss_key_instantiate(struct key *key, const void *p, size_t buflen) +{ + const void *end = (const void *)((const char *)p + buflen); + struct gss_cl_ctx *ctx; + + ctx = gss_key_read_context(p, end); + if (IS_ERR(ctx)) + goto err; + write_lock(&key->lock); + key->payload.data = ctx; + key->expiry = get_seconds() + (ctx->gc_expiry - jiffies)/HZ; + write_unlock(&key->lock); + return 0; +err: + return PTR_ERR(ctx); +} + +static int +gss_key_duplicate(struct key *key, const struct key *source) +{ + struct gss_cl_ctx *ctx = (struct gss_cl_ctx *)source->payload.data; + + if (ctx != NULL) { + gss_mech_get(ctx->gc_gss_ctx->mech_type); + write_lock(&key->lock); + key->payload.data = gss_get_ctx(ctx); + key->expiry = source->expiry; + write_unlock(&key->lock); + } + return 0; +} + +static int +gss_key_update(struct key *key, const void *p, size_t buflen) +{ + const void *end = (const void *)((const char *)p + buflen); + struct gss_cl_ctx *ctx, *old; + + ctx = gss_key_read_context(p, end); + if (IS_ERR(ctx)) + goto err; + write_lock(&key->lock); + old = (struct gss_cl_ctx *) key->payload.data; + key->payload.data = ctx; + key->expiry = get_seconds() + (ctx->gc_expiry - jiffies)/HZ; + write_unlock(&key->lock); + if (old) + gss_put_ctx(ctx); + return 0; +err: + return PTR_ERR(ctx); +} + +static int +gss_key_match(const struct key *key, const void *description) +{ + return key->description != NULL && + strcmp(key->description, description) == 0; +} + +static void +gss_key_destroy(struct key *key) +{ + struct gss_cl_ctx *ctx = (struct gss_cl_ctx *)key->payload.data; + if (ctx != NULL) { + struct gss_api_mech *mech = ctx->gc_gss_ctx->mech_type; + gss_put_ctx(ctx); + gss_mech_put(mech); + } +} + +static void +gss_key_describe(const struct key *key, struct seq_file *m) +{ + struct gss_cl_ctx *ctx = NULL; + + seq_puts(m, key->description); + + if (key->payload.data) + ctx = gss_get_ctx((struct gss_cl_ctx *)key->payload.data); + if (ctx != NULL) { + seq_printf(m, ": %s", ctx->gc_principal); + gss_put_ctx(ctx); + } else + seq_printf(m, ": "); +} + +static struct key_type key_type_rpcsec_context = { + .name = "rpcsec_gss context", + .def_datalen = sizeof(struct gss_cl_ctx) + sizeof(struct gss_ctx), + .instantiate = gss_key_instantiate, + .duplicate = gss_key_duplicate, + .update = gss_key_update, + .match = gss_key_match, + .destroy = gss_key_destroy, + .describe = gss_key_describe, +}; + +static struct key * +gss_request_key(struct gss_auth *gss_auth) +{ + struct key *key; + struct rpc_clnt *clnt = gss_auth->client; + char args[384]; + + snprintf(args, sizeof(args), "%s ip=\"%u.%u.%u.%u\" port=\"%u\" proto=\"%s\"", + gss_auth->key_name, + NIPQUAD(clnt->cl_xprt->addr.sin_addr.s_addr), + clnt->cl_port, + clnt->cl_prot == IPPROTO_TCP ? "tcp" : "udp"); + dprintk("%s: requesting key %s with args %s\n", __FUNCTION__, + gss_auth->key_name, args); + + key = request_key(&key_type_rpcsec_context, gss_auth->key_name, args); + if (IS_ERR(key)) + goto out_err; + dprintk("%s: returned success\n", __FUNCTION__); + return key; +out_err: + dprintk("%s: returned error %ld\n", __FUNCTION__, -PTR_ERR(key)); + return key; +} + + +static inline struct gss_cl_ctx * +gss_key_lookup_context(struct key *key) +{ + struct gss_cl_ctx *ctx = ERR_PTR(-ENOKEY); + + read_lock(&key->lock); + if (key->payload.data != NULL) + ctx = gss_get_ctx((struct gss_cl_ctx *)key->payload.data); + read_unlock(&key->lock); + return ctx; +} + +static inline struct rpc_cred * +gss_key_lookup_cred(struct rpc_auth *auth) +{ + struct gss_auth *gss_auth = container_of(auth, struct gss_auth, rpc_auth); + struct gss_cred *gss_cred; + struct gss_cl_ctx *ctx; + struct key *key; + void *err; + + err = key = gss_request_key(gss_auth); + if (IS_ERR(key)) + goto out_no_key; + err = ctx = gss_key_lookup_context(key); + if (IS_ERR(ctx)) + goto out_put_key; + gss_cred = gss_alloc_cred(gss_auth); + if (gss_cred == NULL) + goto out_no_cred; + gss_cred_set_ctx(&gss_cred->gc_base, ctx); + gss_cred->gc_key = key; + return &gss_cred->gc_base; +out_no_cred: + err = ERR_PTR(-ENOMEM); +out_put_key: + key_put(key); +out_no_key: + return (struct rpc_cred *)err; +} + +static inline int +gss_register_keytype(void) +{ + return register_key_type(&key_type_rpcsec_context); +} + +static inline void +gss_unregister_keytype(void) +{ + unregister_key_type(&key_type_rpcsec_context); +} +#endif + /* * Lookup RPCSEC_GSS cred for the current process */ static struct rpc_cred * gss_lookup_cred(struct rpc_auth *auth, struct auth_cred *acred, int taskflags) { - return rpcauth_lookup_credcache(auth, acred, taskflags); + struct rpc_cred *cred; + + /* Try to use the keyring upcall first */ + cred = gss_key_lookup_cred(auth); + if (!IS_ERR(cred)) + goto out; + switch (PTR_ERR(cred)) { + case -EKEYREVOKED: + case -EKEYEXPIRED: + /* Translate into EACCES */ + cred = ERR_PTR(-EACCES); + break; + case -ENOKEY: + cred = rpcauth_lookup_credcache(auth, acred, taskflags); + }; +out: + return cred; } static struct rpc_cred * @@ -778,25 +1066,14 @@ gss_create_cred(struct rpc_auth *auth, s dprintk("RPC: gss_create_cred for uid %d, flavor %d\n", acred->uid, auth->au_flavor); - if (!(cred = kmalloc(sizeof(*cred), GFP_KERNEL))) + cred = gss_alloc_cred(gss_auth); + if (cred == NULL) goto out_err; - - memset(cred, 0, sizeof(*cred)); - atomic_set(&cred->gc_count, 1); cred->gc_uid = acred->uid; - /* - * Note: in order to force a call to call_refresh(), we deliberately - * fail to flag the credential as RPCAUTH_CRED_UPTODATE. - */ - cred->gc_flags = 0; - cred->gc_base.cr_ops = &gss_credops; - cred->gc_service = gss_auth->service; err = gss_create_upcall(gss_auth, cred); if (err < 0) goto out_err; - return &cred->gc_base; - out_err: dprintk("RPC: gss_create_cred failed with error %d\n", err); if (cred) gss_destroy_cred(&cred->gc_base); @@ -1142,7 +1419,12 @@ static int __init init_rpcsec_gss(void) err = gss_svc_init(); if (err) goto out_unregister; + err = gss_register_keytype(); + if (err) + goto out_shutdown_svc; return 0; +out_shutdown_svc: + gss_svc_shutdown(); out_unregister: rpcauth_unregister(&authgss_ops); out: @@ -1151,6 +1433,7 @@ out: static void __exit exit_rpcsec_gss(void) { + gss_unregister_keytype(); gss_svc_shutdown(); rpcauth_unregister(&authgss_ops); } Index: linux-2.6.13-rc1/include/linux/sunrpc/auth_gss.h =================================================================== --- linux-2.6.13-rc1.orig/include/linux/sunrpc/auth_gss.h +++ linux-2.6.13-rc1/include/linux/sunrpc/auth_gss.h @@ -79,11 +79,13 @@ struct gss_cl_ctx { }; struct gss_upcall_msg; +struct key; struct gss_cred { struct rpc_cred gc_base; enum rpc_gss_svc gc_service; struct gss_cl_ctx *gc_ctx; struct gss_upcall_msg *gc_upcall; + struct key *gc_key; }; #define gc_uid gc_base.cr_uid Index: linux-2.6.13-rc1/fs/Kconfig =================================================================== --- linux-2.6.13-rc1.orig/fs/Kconfig +++ linux-2.6.13-rc1/fs/Kconfig @@ -1526,6 +1526,20 @@ config RPCSEC_GSS_SPKM3 If unsure, say N. +config RPCSEC_GSS_KEYRING + bool "Secure RPC: keyring support (EXPERIMENTAL)" + depends on SUNRPC_GSS && KEYS && EXPERIMENTAL + help + Use the new RPCSEC_GSS upcall mechanism based on keyrings. + This allows individual threads, processes or groups of + processes to specify their own authentication tokens, + providing much the same functionality that AFS pags used to. + + Note: requires the new helper program /sbin/request-key, as + well as an updated rpc.gssd daemon in order to work. + + If unsure, say N + config SMB_FS tristate "SMB file system support (to mount Windows shares etc.)" depends on INET