/* * linux/net/sunrpc/auth.c * * Generic RPC client authentication API. * * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de> */ #include <linux/types.h> #include <linux/sched.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/errno.h> #include <linux/sunrpc/clnt.h> #include <linux/spinlock.h> #ifdef RPC_DEBUG # define RPCDBG_FACILITY RPCDBG_AUTH #endif static struct rpc_authops * auth_flavors[RPC_AUTH_MAXFLAVOR] = { &authnull_ops, /* AUTH_NULL */ &authunix_ops, /* AUTH_UNIX */ NULL, /* others can be loadable modules */ }; static u32 pseudoflavor_to_flavor(u32 flavor) { if (flavor >= RPC_AUTH_MAXFLAVOR) return RPC_AUTH_GSS; return flavor; } int rpcauth_register(struct rpc_authops *ops) { rpc_authflavor_t flavor; if ((flavor = ops->au_flavor) >= RPC_AUTH_MAXFLAVOR) return -EINVAL; if (auth_flavors[flavor] != NULL) return -EPERM; /* what else? */ auth_flavors[flavor] = ops; return 0; } int rpcauth_unregister(struct rpc_authops *ops) { rpc_authflavor_t flavor; if ((flavor = ops->au_flavor) >= RPC_AUTH_MAXFLAVOR) return -EINVAL; if (auth_flavors[flavor] != ops) return -EPERM; /* what else? */ auth_flavors[flavor] = NULL; return 0; } struct rpc_auth * rpcauth_create(rpc_authflavor_t pseudoflavor, struct rpc_clnt *clnt) { struct rpc_auth *auth; struct rpc_authops *ops; u32 flavor = pseudoflavor_to_flavor(pseudoflavor); auth = ERR_PTR(-EINVAL); if (flavor >= RPC_AUTH_MAXFLAVOR) goto out; /* FIXME - auth_flavors[] really needs an rw lock, * and module refcounting. */ #ifdef CONFIG_KMOD if ((ops = auth_flavors[flavor]) == NULL) request_module("rpc-auth-%u", flavor); #endif if ((ops = auth_flavors[flavor]) == NULL) goto out; auth = ops->create(clnt, pseudoflavor); if (IS_ERR(auth)) return auth; if (clnt->cl_auth) rpcauth_destroy(clnt->cl_auth); clnt->cl_auth = auth; out: return auth; } void rpcauth_destroy(struct rpc_auth *auth) { if (!atomic_dec_and_test(&auth->au_count)) return; auth->au_ops->destroy(auth); } static DEFINE_SPINLOCK(rpc_credcache_lock); /* * Initialize RPC credential cache */ int rpcauth_init_credcache(struct rpc_auth *auth, unsigned long expire) { struct rpc_cred_cache *new; int i; new = kmalloc(sizeof(*new), GFP_KERNEL); if (!new) return -ENOMEM; for (i = 0; i < RPC_CREDCACHE_NR; i++) INIT_HLIST_HEAD(&new->hashtable[i]); new->expire = expire; new->nextgc = jiffies + (expire >> 1); auth->au_credcache = new; return 0; } /* * Destroy a list of credentials */ static inline void rpcauth_destroy_credlist(struct hlist_head *head) { struct rpc_cred *cred; while (!hlist_empty(head)) { cred = hlist_entry(head->first, struct rpc_cred, cr_hash); hlist_del_init(&cred->cr_hash); put_rpccred(cred); } } /* * Clear the RPC credential cache, and delete those credentials * that are not referenced. */ void rpcauth_free_credcache(struct rpc_auth *auth) { struct rpc_cred_cache *cache = auth->au_credcache; HLIST_HEAD(free); struct hlist_node *pos, *next; struct rpc_cred *cred; int i; spin_lock(&rpc_credcache_lock); for (i = 0; i < RPC_CREDCACHE_NR; i++) { hlist_for_each_safe(pos, next, &cache->hashtable[i]) { cred = hlist_entry(pos, struct rpc_cred, cr_hash); __hlist_del(&cred->cr_hash); hlist_add_head(&cred->cr_hash, &free); } } spin_unlock(&rpc_credcache_lock); rpcauth_destroy_credlist(&free); } static void rpcauth_prune_expired(struct rpc_auth *auth, struct rpc_cred *cred, struct hlist_head *free) { if (atomic_read(&cred->cr_count) != 1) return; if (time_after(jiffies, cred->cr_expire + auth->au_credcache->expire)) cred->cr_flags &= ~RPCAUTH_CRED_UPTODATE; if (!(cred->cr_flags & RPCAUTH_CRED_UPTODATE)) { __hlist_del(&cred->cr_hash); hlist_add_head(&cred->cr_hash, free); } } /* * Remove stale credentials. Avoid sleeping inside the loop. */ static void rpcauth_gc_credcache(struct rpc_auth *auth, struct hlist_head *free) { struct rpc_cred_cache *cache = auth->au_credcache; struct hlist_node *pos, *next; struct rpc_cred *cred; int i; dprintk("RPC: gc'ing RPC credentials for auth %p\n", auth); for (i = 0; i < RPC_CREDCACHE_NR; i++) { hlist_for_each_safe(pos, next, &cache->hashtable[i]) { cred = hlist_entry(pos, struct rpc_cred, cr_hash); rpcauth_prune_expired(auth, cred, free); } } cache->nextgc = jiffies + cache->expire; } /* * Look up a process' credentials in the authentication cache */ struct rpc_cred * rpcauth_lookup_credcache(struct rpc_auth *auth, struct auth_cred * acred, int flags) { struct rpc_cred_cache *cache = auth->au_credcache; HLIST_HEAD(free); struct hlist_node *pos, *next; struct rpc_cred *new = NULL, *cred = NULL; int nr = 0; if (!(flags & RPCAUTH_LOOKUP_ROOTCREDS)) nr = acred->uid & RPC_CREDCACHE_MASK; retry: spin_lock(&rpc_credcache_lock); if (time_before(cache->nextgc, jiffies)) rpcauth_gc_credcache(auth, &free); hlist_for_each_safe(pos, next, &cache->hashtable[nr]) { struct rpc_cred *entry; entry = hlist_entry(pos, struct rpc_cred, cr_hash); if (entry->cr_ops->crmatch(acred, entry, flags)) { hlist_del(&entry->cr_hash); cred = entry; break; } rpcauth_prune_expired(auth, entry, &free); } if (new) { if (cred) hlist_add_head(&new->cr_hash, &free); else cred = new; } if (cred) { hlist_add_head(&cred->cr_hash, &cache->hashtable[nr]); get_rpccred(cred); } spin_unlock(&rpc_credcache_lock); rpcauth_destroy_credlist(&free); if (!cred) { new = auth->au_ops->crcreate(auth, acred, flags); if (!IS_ERR(new)) { #ifdef RPC_DEBUG new->cr_magic = RPCAUTH_CRED_MAGIC; #endif goto retry; } else cred = new; } else if ((cred->cr_flags & RPCAUTH_CRED_NEW) && cred->cr_ops->cr_init != NULL && !(flags & RPCAUTH_LOOKUP_NEW)) { int res = cred->cr_ops->cr_init(auth, cred); if (res < 0) { put_rpccred(cred); cred = ERR_PTR(res); } } return (struct rpc_cred *) cred; } struct rpc_cred * rpcauth_lookupcred(struct rpc_auth *auth, int flags) { struct auth_cred acred = { .uid = current->fsuid, .gid = current->fsgid, .group_info = current->group_info, }; struct rpc_cred *ret; dprintk("RPC: looking up %s cred\n", auth->au_ops->au_name); get_group_info(acred.group_info); ret = auth->au_ops->lookup_cred(auth, &acred, flags); put_group_info(acred.group_info); return ret; } struct rpc_cred * rpcauth_bindcred(struct rpc_task *task) { struct rpc_auth *auth = task->tk_auth; struct auth_cred acred = { .uid = current->fsuid, .gid = current->fsgid, .group_info = current->group_info, }; struct rpc_cred *ret; int flags = 0; dprintk("RPC: %5u looking up %s cred\n", task->tk_pid, task->tk_auth->au_ops->au_name); get_group_info(acred.group_info); if (task->tk_flags & RPC_TASK_ROOTCREDS) flags |= RPCAUTH_LOOKUP_ROOTCREDS; ret = auth->au_ops->lookup_cred(auth, &acred, flags); if (!IS_ERR(ret)) task->tk_msg.rpc_cred = ret; else task->tk_status = PTR_ERR(ret); put_group_info(acred.group_info); return ret; } void rpcauth_holdcred(struct rpc_task *task) { dprintk("RPC: %5u holding %s cred %p\n", task->tk_pid, task->tk_auth->au_ops->au_name, task->tk_msg.rpc_cred); if (task->tk_msg.rpc_cred) get_rpccred(task->tk_msg.rpc_cred); } void put_rpccred(struct rpc_cred *cred) { cred->cr_expire = jiffies; if (!atomic_dec_and_test(&cred->cr_count)) return; cred->cr_ops->crdestroy(cred); } void rpcauth_unbindcred(struct rpc_task *task) { struct rpc_cred *cred = task->tk_msg.rpc_cred; dprintk("RPC: %5u releasing %s cred %p\n", task->tk_pid, task->tk_auth->au_ops->au_name, cred); put_rpccred(cred); task->tk_msg.rpc_cred = NULL; } __be32 * rpcauth_marshcred(struct rpc_task *task, __be32 *p) { struct rpc_cred *cred = task->tk_msg.rpc_cred; dprintk("RPC: %5u marshaling %s cred %p\n", task->tk_pid, task->tk_auth->au_ops->au_name, cred); return cred->cr_ops->crmarshal(task, p); } __be32 * rpcauth_checkverf(struct rpc_task *task, __be32 *p) { struct rpc_cred *cred = task->tk_msg.rpc_cred; dprintk("RPC: %5u validating %s cred %p\n", task->tk_pid, task->tk_auth->au_ops->au_name, cred); return cred->cr_ops->crvalidate(task, p); } int rpcauth_wrap_req(struct rpc_task *task, kxdrproc_t encode, void *rqstp, __be32 *data, void *obj) { struct rpc_cred *cred = task->tk_msg.rpc_cred; dprintk("RPC: %5u using %s cred %p to wrap rpc data\n", task->tk_pid, cred->cr_ops->cr_name, cred); if (cred->cr_ops->crwrap_req) return cred->cr_ops->crwrap_req(task, encode, rqstp, data, obj); /* By default, we encode the arguments normally. */ return encode(rqstp, data, obj); } int rpcauth_unwrap_resp(struct rpc_task *task, kxdrproc_t decode, void *rqstp, __be32 *data, void *obj) { struct rpc_cred *cred = task->tk_msg.rpc_cred; dprintk("RPC: %5u using %s cred %p to unwrap rpc data\n", task->tk_pid, cred->cr_ops->cr_name, cred); if (cred->cr_ops->crunwrap_resp) return cred->cr_ops->crunwrap_resp(task, decode, rqstp, data, obj); /* By default, we decode the arguments normally. */ return decode(rqstp, data, obj); } int rpcauth_refreshcred(struct rpc_task *task) { struct rpc_cred *cred = task->tk_msg.rpc_cred; int err; dprintk("RPC: %5u refreshing %s cred %p\n", task->tk_pid, task->tk_auth->au_ops->au_name, cred); err = cred->cr_ops->crrefresh(task); if (err < 0) task->tk_status = err; return err; } void rpcauth_invalcred(struct rpc_task *task) { dprintk("RPC: %5u invalidating %s cred %p\n", task->tk_pid, task->tk_auth->au_ops->au_name, task->tk_msg.rpc_cred); spin_lock(&rpc_credcache_lock); if (task->tk_msg.rpc_cred) task->tk_msg.rpc_cred->cr_flags &= ~RPCAUTH_CRED_UPTODATE; spin_unlock(&rpc_credcache_lock); } int rpcauth_uptodatecred(struct rpc_task *task) { return !(task->tk_msg.rpc_cred) || (task->tk_msg.rpc_cred->cr_flags & RPCAUTH_CRED_UPTODATE); }