11da177e4SLinus Torvalds /* 21da177e4SLinus Torvalds * linux/net/sunrpc/auth.c 31da177e4SLinus Torvalds * 41da177e4SLinus Torvalds * Generic RPC client authentication API. 51da177e4SLinus Torvalds * 61da177e4SLinus Torvalds * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de> 71da177e4SLinus Torvalds */ 81da177e4SLinus Torvalds 91da177e4SLinus Torvalds #include <linux/types.h> 101da177e4SLinus Torvalds #include <linux/sched.h> 111da177e4SLinus Torvalds #include <linux/module.h> 121da177e4SLinus Torvalds #include <linux/slab.h> 131da177e4SLinus Torvalds #include <linux/errno.h> 1425337fdcSTrond Myklebust #include <linux/hash.h> 151da177e4SLinus Torvalds #include <linux/sunrpc/clnt.h> 166a1a1e34SChuck Lever #include <linux/sunrpc/gss_api.h> 171da177e4SLinus Torvalds #include <linux/spinlock.h> 181da177e4SLinus Torvalds 191da177e4SLinus Torvalds #ifdef RPC_DEBUG 201da177e4SLinus Torvalds # define RPCDBG_FACILITY RPCDBG_AUTH 211da177e4SLinus Torvalds #endif 221da177e4SLinus Torvalds 23241269bdSTrond Myklebust #define RPC_CREDCACHE_DEFAULT_HASHBITS (4) 24241269bdSTrond Myklebust struct rpc_cred_cache { 25241269bdSTrond Myklebust struct hlist_head *hashtable; 26241269bdSTrond Myklebust unsigned int hashbits; 27241269bdSTrond Myklebust spinlock_t lock; 28241269bdSTrond Myklebust }; 29241269bdSTrond Myklebust 30241269bdSTrond Myklebust static unsigned int auth_hashbits = RPC_CREDCACHE_DEFAULT_HASHBITS; 31241269bdSTrond Myklebust 32fc1b356fSTrond Myklebust static DEFINE_SPINLOCK(rpc_authflavor_lock); 33f1c0a861STrond Myklebust static const struct rpc_authops *auth_flavors[RPC_AUTH_MAXFLAVOR] = { 341da177e4SLinus Torvalds &authnull_ops, /* AUTH_NULL */ 351da177e4SLinus Torvalds &authunix_ops, /* AUTH_UNIX */ 361da177e4SLinus Torvalds NULL, /* others can be loadable modules */ 371da177e4SLinus Torvalds }; 381da177e4SLinus Torvalds 39e092bdcdSTrond Myklebust static LIST_HEAD(cred_unused); 40f5c2187cSTrond Myklebust static unsigned long number_cred_unused; 41e092bdcdSTrond Myklebust 42db5fe265SMiquel van Smoorenburg #define MAX_HASHTABLE_BITS (14) 438e4e15d4SStephen Rothwell static int param_set_hashtbl_sz(const char *val, const struct kernel_param *kp) 44241269bdSTrond Myklebust { 45241269bdSTrond Myklebust unsigned long num; 46241269bdSTrond Myklebust unsigned int nbits; 47241269bdSTrond Myklebust int ret; 48241269bdSTrond Myklebust 49241269bdSTrond Myklebust if (!val) 50241269bdSTrond Myklebust goto out_inval; 51241269bdSTrond Myklebust ret = strict_strtoul(val, 0, &num); 52241269bdSTrond Myklebust if (ret == -EINVAL) 53241269bdSTrond Myklebust goto out_inval; 54241269bdSTrond Myklebust nbits = fls(num); 55241269bdSTrond Myklebust if (num > (1U << nbits)) 56241269bdSTrond Myklebust nbits++; 57241269bdSTrond Myklebust if (nbits > MAX_HASHTABLE_BITS || nbits < 2) 58241269bdSTrond Myklebust goto out_inval; 59241269bdSTrond Myklebust *(unsigned int *)kp->arg = nbits; 60241269bdSTrond Myklebust return 0; 61241269bdSTrond Myklebust out_inval: 62241269bdSTrond Myklebust return -EINVAL; 63241269bdSTrond Myklebust } 64241269bdSTrond Myklebust 658e4e15d4SStephen Rothwell static int param_get_hashtbl_sz(char *buffer, const struct kernel_param *kp) 66241269bdSTrond Myklebust { 67241269bdSTrond Myklebust unsigned int nbits; 68241269bdSTrond Myklebust 69241269bdSTrond Myklebust nbits = *(unsigned int *)kp->arg; 70241269bdSTrond Myklebust return sprintf(buffer, "%u", 1U << nbits); 71241269bdSTrond Myklebust } 72241269bdSTrond Myklebust 73241269bdSTrond Myklebust #define param_check_hashtbl_sz(name, p) __param_check(name, p, unsigned int); 74241269bdSTrond Myklebust 758e4e15d4SStephen Rothwell static struct kernel_param_ops param_ops_hashtbl_sz = { 768e4e15d4SStephen Rothwell .set = param_set_hashtbl_sz, 778e4e15d4SStephen Rothwell .get = param_get_hashtbl_sz, 788e4e15d4SStephen Rothwell }; 798e4e15d4SStephen Rothwell 80241269bdSTrond Myklebust module_param_named(auth_hashtable_size, auth_hashbits, hashtbl_sz, 0644); 81241269bdSTrond Myklebust MODULE_PARM_DESC(auth_hashtable_size, "RPC credential cache hashtable size"); 82241269bdSTrond Myklebust 831da177e4SLinus Torvalds static u32 841da177e4SLinus Torvalds pseudoflavor_to_flavor(u32 flavor) { 851da177e4SLinus Torvalds if (flavor >= RPC_AUTH_MAXFLAVOR) 861da177e4SLinus Torvalds return RPC_AUTH_GSS; 871da177e4SLinus Torvalds return flavor; 881da177e4SLinus Torvalds } 891da177e4SLinus Torvalds 901da177e4SLinus Torvalds int 91f1c0a861STrond Myklebust rpcauth_register(const struct rpc_authops *ops) 921da177e4SLinus Torvalds { 931da177e4SLinus Torvalds rpc_authflavor_t flavor; 94fc1b356fSTrond Myklebust int ret = -EPERM; 951da177e4SLinus Torvalds 961da177e4SLinus Torvalds if ((flavor = ops->au_flavor) >= RPC_AUTH_MAXFLAVOR) 971da177e4SLinus Torvalds return -EINVAL; 98fc1b356fSTrond Myklebust spin_lock(&rpc_authflavor_lock); 99fc1b356fSTrond Myklebust if (auth_flavors[flavor] == NULL) { 1001da177e4SLinus Torvalds auth_flavors[flavor] = ops; 101fc1b356fSTrond Myklebust ret = 0; 102fc1b356fSTrond Myklebust } 103fc1b356fSTrond Myklebust spin_unlock(&rpc_authflavor_lock); 104fc1b356fSTrond Myklebust return ret; 1051da177e4SLinus Torvalds } 106e8914c65STrond Myklebust EXPORT_SYMBOL_GPL(rpcauth_register); 1071da177e4SLinus Torvalds 1081da177e4SLinus Torvalds int 109f1c0a861STrond Myklebust rpcauth_unregister(const struct rpc_authops *ops) 1101da177e4SLinus Torvalds { 1111da177e4SLinus Torvalds rpc_authflavor_t flavor; 112fc1b356fSTrond Myklebust int ret = -EPERM; 1131da177e4SLinus Torvalds 1141da177e4SLinus Torvalds if ((flavor = ops->au_flavor) >= RPC_AUTH_MAXFLAVOR) 1151da177e4SLinus Torvalds return -EINVAL; 116fc1b356fSTrond Myklebust spin_lock(&rpc_authflavor_lock); 117fc1b356fSTrond Myklebust if (auth_flavors[flavor] == ops) { 1181da177e4SLinus Torvalds auth_flavors[flavor] = NULL; 119fc1b356fSTrond Myklebust ret = 0; 120fc1b356fSTrond Myklebust } 121fc1b356fSTrond Myklebust spin_unlock(&rpc_authflavor_lock); 122fc1b356fSTrond Myklebust return ret; 1231da177e4SLinus Torvalds } 124e8914c65STrond Myklebust EXPORT_SYMBOL_GPL(rpcauth_unregister); 1251da177e4SLinus Torvalds 1266a1a1e34SChuck Lever /** 1276a1a1e34SChuck Lever * rpcauth_list_flavors - discover registered flavors and pseudoflavors 1286a1a1e34SChuck Lever * @array: array to fill in 1296a1a1e34SChuck Lever * @size: size of "array" 1306a1a1e34SChuck Lever * 1316a1a1e34SChuck Lever * Returns the number of array items filled in, or a negative errno. 1326a1a1e34SChuck Lever * 1336a1a1e34SChuck Lever * The returned array is not sorted by any policy. Callers should not 1346a1a1e34SChuck Lever * rely on the order of the items in the returned array. 1356a1a1e34SChuck Lever */ 1366a1a1e34SChuck Lever int 1376a1a1e34SChuck Lever rpcauth_list_flavors(rpc_authflavor_t *array, int size) 1386a1a1e34SChuck Lever { 1396a1a1e34SChuck Lever rpc_authflavor_t flavor; 1406a1a1e34SChuck Lever int result = 0; 1416a1a1e34SChuck Lever 1426a1a1e34SChuck Lever spin_lock(&rpc_authflavor_lock); 1436a1a1e34SChuck Lever for (flavor = 0; flavor < RPC_AUTH_MAXFLAVOR; flavor++) { 1446a1a1e34SChuck Lever const struct rpc_authops *ops = auth_flavors[flavor]; 1456a1a1e34SChuck Lever rpc_authflavor_t pseudos[4]; 1466a1a1e34SChuck Lever int i, len; 1476a1a1e34SChuck Lever 1486a1a1e34SChuck Lever if (result >= size) { 1496a1a1e34SChuck Lever result = -ENOMEM; 1506a1a1e34SChuck Lever break; 1516a1a1e34SChuck Lever } 1526a1a1e34SChuck Lever 1536a1a1e34SChuck Lever if (ops == NULL) 1546a1a1e34SChuck Lever continue; 1556a1a1e34SChuck Lever if (ops->list_pseudoflavors == NULL) { 1566a1a1e34SChuck Lever array[result++] = ops->au_flavor; 1576a1a1e34SChuck Lever continue; 1586a1a1e34SChuck Lever } 1596a1a1e34SChuck Lever len = ops->list_pseudoflavors(pseudos, ARRAY_SIZE(pseudos)); 1606a1a1e34SChuck Lever if (len < 0) { 1616a1a1e34SChuck Lever result = len; 1626a1a1e34SChuck Lever break; 1636a1a1e34SChuck Lever } 1646a1a1e34SChuck Lever for (i = 0; i < len; i++) { 1656a1a1e34SChuck Lever if (result >= size) { 1666a1a1e34SChuck Lever result = -ENOMEM; 1676a1a1e34SChuck Lever break; 1686a1a1e34SChuck Lever } 1696a1a1e34SChuck Lever array[result++] = pseudos[i]; 1706a1a1e34SChuck Lever } 1716a1a1e34SChuck Lever } 1726a1a1e34SChuck Lever spin_unlock(&rpc_authflavor_lock); 1736a1a1e34SChuck Lever 1746a1a1e34SChuck Lever dprintk("RPC: %s returns %d\n", __func__, result); 1756a1a1e34SChuck Lever return result; 1766a1a1e34SChuck Lever } 1776a1a1e34SChuck Lever EXPORT_SYMBOL_GPL(rpcauth_list_flavors); 1786a1a1e34SChuck Lever 1791da177e4SLinus Torvalds struct rpc_auth * 1801da177e4SLinus Torvalds rpcauth_create(rpc_authflavor_t pseudoflavor, struct rpc_clnt *clnt) 1811da177e4SLinus Torvalds { 1821da177e4SLinus Torvalds struct rpc_auth *auth; 183f1c0a861STrond Myklebust const struct rpc_authops *ops; 1841da177e4SLinus Torvalds u32 flavor = pseudoflavor_to_flavor(pseudoflavor); 1851da177e4SLinus Torvalds 186f344f6dfSOlaf Kirch auth = ERR_PTR(-EINVAL); 187f344f6dfSOlaf Kirch if (flavor >= RPC_AUTH_MAXFLAVOR) 188f344f6dfSOlaf Kirch goto out; 189f344f6dfSOlaf Kirch 190f344f6dfSOlaf Kirch if ((ops = auth_flavors[flavor]) == NULL) 191f344f6dfSOlaf Kirch request_module("rpc-auth-%u", flavor); 192fc1b356fSTrond Myklebust spin_lock(&rpc_authflavor_lock); 193fc1b356fSTrond Myklebust ops = auth_flavors[flavor]; 194fc1b356fSTrond Myklebust if (ops == NULL || !try_module_get(ops->owner)) { 195fc1b356fSTrond Myklebust spin_unlock(&rpc_authflavor_lock); 196f344f6dfSOlaf Kirch goto out; 197fc1b356fSTrond Myklebust } 198fc1b356fSTrond Myklebust spin_unlock(&rpc_authflavor_lock); 1991da177e4SLinus Torvalds auth = ops->create(clnt, pseudoflavor); 200fc1b356fSTrond Myklebust module_put(ops->owner); 2016a19275aSJ. Bruce Fields if (IS_ERR(auth)) 2026a19275aSJ. Bruce Fields return auth; 2031da177e4SLinus Torvalds if (clnt->cl_auth) 204de7a8ce3STrond Myklebust rpcauth_release(clnt->cl_auth); 2051da177e4SLinus Torvalds clnt->cl_auth = auth; 206f344f6dfSOlaf Kirch 207f344f6dfSOlaf Kirch out: 2081da177e4SLinus Torvalds return auth; 2091da177e4SLinus Torvalds } 210e8914c65STrond Myklebust EXPORT_SYMBOL_GPL(rpcauth_create); 2111da177e4SLinus Torvalds 2121da177e4SLinus Torvalds void 213de7a8ce3STrond Myklebust rpcauth_release(struct rpc_auth *auth) 2141da177e4SLinus Torvalds { 2151da177e4SLinus Torvalds if (!atomic_dec_and_test(&auth->au_count)) 2161da177e4SLinus Torvalds return; 2171da177e4SLinus Torvalds auth->au_ops->destroy(auth); 2181da177e4SLinus Torvalds } 2191da177e4SLinus Torvalds 2201da177e4SLinus Torvalds static DEFINE_SPINLOCK(rpc_credcache_lock); 2211da177e4SLinus Torvalds 22231be5bf1STrond Myklebust static void 22331be5bf1STrond Myklebust rpcauth_unhash_cred_locked(struct rpc_cred *cred) 22431be5bf1STrond Myklebust { 22531be5bf1STrond Myklebust hlist_del_rcu(&cred->cr_hash); 22631be5bf1STrond Myklebust smp_mb__before_clear_bit(); 22731be5bf1STrond Myklebust clear_bit(RPCAUTH_CRED_HASHED, &cred->cr_flags); 22831be5bf1STrond Myklebust } 22931be5bf1STrond Myklebust 230f0380f3dSTrond Myklebust static int 2319499b434STrond Myklebust rpcauth_unhash_cred(struct rpc_cred *cred) 2329499b434STrond Myklebust { 2339499b434STrond Myklebust spinlock_t *cache_lock; 234f0380f3dSTrond Myklebust int ret; 2359499b434STrond Myklebust 2369499b434STrond Myklebust cache_lock = &cred->cr_auth->au_credcache->lock; 2379499b434STrond Myklebust spin_lock(cache_lock); 238f0380f3dSTrond Myklebust ret = atomic_read(&cred->cr_count) == 0; 239f0380f3dSTrond Myklebust if (ret) 2409499b434STrond Myklebust rpcauth_unhash_cred_locked(cred); 2419499b434STrond Myklebust spin_unlock(cache_lock); 242f0380f3dSTrond Myklebust return ret; 2439499b434STrond Myklebust } 2449499b434STrond Myklebust 2451da177e4SLinus Torvalds /* 2461da177e4SLinus Torvalds * Initialize RPC credential cache 2471da177e4SLinus Torvalds */ 2481da177e4SLinus Torvalds int 249f5c2187cSTrond Myklebust rpcauth_init_credcache(struct rpc_auth *auth) 2501da177e4SLinus Torvalds { 2511da177e4SLinus Torvalds struct rpc_cred_cache *new; 252988664a0STrond Myklebust unsigned int hashsize; 2531da177e4SLinus Torvalds 2548b3a7005SKris Katterjohn new = kmalloc(sizeof(*new), GFP_KERNEL); 2551da177e4SLinus Torvalds if (!new) 256241269bdSTrond Myklebust goto out_nocache; 257241269bdSTrond Myklebust new->hashbits = auth_hashbits; 258988664a0STrond Myklebust hashsize = 1U << new->hashbits; 259241269bdSTrond Myklebust new->hashtable = kcalloc(hashsize, sizeof(new->hashtable[0]), GFP_KERNEL); 260241269bdSTrond Myklebust if (!new->hashtable) 261241269bdSTrond Myklebust goto out_nohashtbl; 2629499b434STrond Myklebust spin_lock_init(&new->lock); 2631da177e4SLinus Torvalds auth->au_credcache = new; 2641da177e4SLinus Torvalds return 0; 265241269bdSTrond Myklebust out_nohashtbl: 266241269bdSTrond Myklebust kfree(new); 267241269bdSTrond Myklebust out_nocache: 268241269bdSTrond Myklebust return -ENOMEM; 2691da177e4SLinus Torvalds } 270e8914c65STrond Myklebust EXPORT_SYMBOL_GPL(rpcauth_init_credcache); 2711da177e4SLinus Torvalds 2721da177e4SLinus Torvalds /* 2731da177e4SLinus Torvalds * Destroy a list of credentials 2741da177e4SLinus Torvalds */ 2751da177e4SLinus Torvalds static inline 276e092bdcdSTrond Myklebust void rpcauth_destroy_credlist(struct list_head *head) 2771da177e4SLinus Torvalds { 2781da177e4SLinus Torvalds struct rpc_cred *cred; 2791da177e4SLinus Torvalds 280e092bdcdSTrond Myklebust while (!list_empty(head)) { 281e092bdcdSTrond Myklebust cred = list_entry(head->next, struct rpc_cred, cr_lru); 282e092bdcdSTrond Myklebust list_del_init(&cred->cr_lru); 2831da177e4SLinus Torvalds put_rpccred(cred); 2841da177e4SLinus Torvalds } 2851da177e4SLinus Torvalds } 2861da177e4SLinus Torvalds 2871da177e4SLinus Torvalds /* 2881da177e4SLinus Torvalds * Clear the RPC credential cache, and delete those credentials 2891da177e4SLinus Torvalds * that are not referenced. 2901da177e4SLinus Torvalds */ 2911da177e4SLinus Torvalds void 2923ab9bb72STrond Myklebust rpcauth_clear_credcache(struct rpc_cred_cache *cache) 2931da177e4SLinus Torvalds { 294e092bdcdSTrond Myklebust LIST_HEAD(free); 295e092bdcdSTrond Myklebust struct hlist_head *head; 2961da177e4SLinus Torvalds struct rpc_cred *cred; 297988664a0STrond Myklebust unsigned int hashsize = 1U << cache->hashbits; 2981da177e4SLinus Torvalds int i; 2991da177e4SLinus Torvalds 3001da177e4SLinus Torvalds spin_lock(&rpc_credcache_lock); 3019499b434STrond Myklebust spin_lock(&cache->lock); 302988664a0STrond Myklebust for (i = 0; i < hashsize; i++) { 303e092bdcdSTrond Myklebust head = &cache->hashtable[i]; 304e092bdcdSTrond Myklebust while (!hlist_empty(head)) { 305e092bdcdSTrond Myklebust cred = hlist_entry(head->first, struct rpc_cred, cr_hash); 306e092bdcdSTrond Myklebust get_rpccred(cred); 307f5c2187cSTrond Myklebust if (!list_empty(&cred->cr_lru)) { 308f5c2187cSTrond Myklebust list_del(&cred->cr_lru); 309f5c2187cSTrond Myklebust number_cred_unused--; 310f5c2187cSTrond Myklebust } 311f5c2187cSTrond Myklebust list_add_tail(&cred->cr_lru, &free); 31231be5bf1STrond Myklebust rpcauth_unhash_cred_locked(cred); 3131da177e4SLinus Torvalds } 3141da177e4SLinus Torvalds } 3159499b434STrond Myklebust spin_unlock(&cache->lock); 3161da177e4SLinus Torvalds spin_unlock(&rpc_credcache_lock); 3171da177e4SLinus Torvalds rpcauth_destroy_credlist(&free); 3181da177e4SLinus Torvalds } 3191da177e4SLinus Torvalds 3203ab9bb72STrond Myklebust /* 3213ab9bb72STrond Myklebust * Destroy the RPC credential cache 3223ab9bb72STrond Myklebust */ 3233ab9bb72STrond Myklebust void 3243ab9bb72STrond Myklebust rpcauth_destroy_credcache(struct rpc_auth *auth) 3253ab9bb72STrond Myklebust { 3263ab9bb72STrond Myklebust struct rpc_cred_cache *cache = auth->au_credcache; 3273ab9bb72STrond Myklebust 3283ab9bb72STrond Myklebust if (cache) { 3293ab9bb72STrond Myklebust auth->au_credcache = NULL; 3303ab9bb72STrond Myklebust rpcauth_clear_credcache(cache); 331241269bdSTrond Myklebust kfree(cache->hashtable); 3323ab9bb72STrond Myklebust kfree(cache); 3333ab9bb72STrond Myklebust } 3343ab9bb72STrond Myklebust } 335e8914c65STrond Myklebust EXPORT_SYMBOL_GPL(rpcauth_destroy_credcache); 3363ab9bb72STrond Myklebust 337d2b83141STrond Myklebust 338d2b83141STrond Myklebust #define RPC_AUTH_EXPIRY_MORATORIUM (60 * HZ) 339d2b83141STrond Myklebust 3401da177e4SLinus Torvalds /* 3411da177e4SLinus Torvalds * Remove stale credentials. Avoid sleeping inside the loop. 3421da177e4SLinus Torvalds */ 343f5c2187cSTrond Myklebust static int 344f5c2187cSTrond Myklebust rpcauth_prune_expired(struct list_head *free, int nr_to_scan) 3451da177e4SLinus Torvalds { 3469499b434STrond Myklebust spinlock_t *cache_lock; 347eac0d18dSTrond Myklebust struct rpc_cred *cred, *next; 348d2b83141STrond Myklebust unsigned long expired = jiffies - RPC_AUTH_EXPIRY_MORATORIUM; 3491da177e4SLinus Torvalds 350eac0d18dSTrond Myklebust list_for_each_entry_safe(cred, next, &cred_unused, cr_lru) { 351eac0d18dSTrond Myklebust 35220673406STrond Myklebust if (nr_to_scan-- == 0) 35320673406STrond Myklebust break; 35493a05e65STrond Myklebust /* 35593a05e65STrond Myklebust * Enforce a 60 second garbage collection moratorium 35693a05e65STrond Myklebust * Note that the cred_unused list must be time-ordered. 35793a05e65STrond Myklebust */ 3583d7b0894STrond Myklebust if (time_in_range(cred->cr_expire, expired, jiffies) && 359eac0d18dSTrond Myklebust test_bit(RPCAUTH_CRED_HASHED, &cred->cr_flags) != 0) 36093a05e65STrond Myklebust return 0; 361eac0d18dSTrond Myklebust 362e092bdcdSTrond Myklebust list_del_init(&cred->cr_lru); 363f5c2187cSTrond Myklebust number_cred_unused--; 364e092bdcdSTrond Myklebust if (atomic_read(&cred->cr_count) != 0) 365e092bdcdSTrond Myklebust continue; 366eac0d18dSTrond Myklebust 3679499b434STrond Myklebust cache_lock = &cred->cr_auth->au_credcache->lock; 3689499b434STrond Myklebust spin_lock(cache_lock); 3699499b434STrond Myklebust if (atomic_read(&cred->cr_count) == 0) { 370e092bdcdSTrond Myklebust get_rpccred(cred); 371e092bdcdSTrond Myklebust list_add_tail(&cred->cr_lru, free); 37231be5bf1STrond Myklebust rpcauth_unhash_cred_locked(cred); 3731da177e4SLinus Torvalds } 3749499b434STrond Myklebust spin_unlock(cache_lock); 3759499b434STrond Myklebust } 37693a05e65STrond Myklebust return (number_cred_unused / 100) * sysctl_vfs_cache_pressure; 3771da177e4SLinus Torvalds } 378e092bdcdSTrond Myklebust 379e092bdcdSTrond Myklebust /* 380f5c2187cSTrond Myklebust * Run memory cache shrinker. 381e092bdcdSTrond Myklebust */ 382f5c2187cSTrond Myklebust static int 3831495f230SYing Han rpcauth_cache_shrinker(struct shrinker *shrink, struct shrink_control *sc) 384e092bdcdSTrond Myklebust { 385f5c2187cSTrond Myklebust LIST_HEAD(free); 386f5c2187cSTrond Myklebust int res; 3871495f230SYing Han int nr_to_scan = sc->nr_to_scan; 3881495f230SYing Han gfp_t gfp_mask = sc->gfp_mask; 389f5c2187cSTrond Myklebust 390d300a41eSTrond Myklebust if ((gfp_mask & GFP_KERNEL) != GFP_KERNEL) 391d300a41eSTrond Myklebust return (nr_to_scan == 0) ? 0 : -1; 392f5c2187cSTrond Myklebust if (list_empty(&cred_unused)) 393f5c2187cSTrond Myklebust return 0; 39431be5bf1STrond Myklebust spin_lock(&rpc_credcache_lock); 39593a05e65STrond Myklebust res = rpcauth_prune_expired(&free, nr_to_scan); 39631be5bf1STrond Myklebust spin_unlock(&rpc_credcache_lock); 397f5c2187cSTrond Myklebust rpcauth_destroy_credlist(&free); 398f5c2187cSTrond Myklebust return res; 3991da177e4SLinus Torvalds } 4001da177e4SLinus Torvalds 4011da177e4SLinus Torvalds /* 4021da177e4SLinus Torvalds * Look up a process' credentials in the authentication cache 4031da177e4SLinus Torvalds */ 4041da177e4SLinus Torvalds struct rpc_cred * 4051da177e4SLinus Torvalds rpcauth_lookup_credcache(struct rpc_auth *auth, struct auth_cred * acred, 4068a317760STrond Myklebust int flags) 4071da177e4SLinus Torvalds { 408e092bdcdSTrond Myklebust LIST_HEAD(free); 4091da177e4SLinus Torvalds struct rpc_cred_cache *cache = auth->au_credcache; 410e092bdcdSTrond Myklebust struct hlist_node *pos; 41131be5bf1STrond Myklebust struct rpc_cred *cred = NULL, 41231be5bf1STrond Myklebust *entry, *new; 41325337fdcSTrond Myklebust unsigned int nr; 41425337fdcSTrond Myklebust 4159e469e30SEric W. Biederman nr = hash_long(from_kuid(&init_user_ns, acred->uid), cache->hashbits); 4161da177e4SLinus Torvalds 41731be5bf1STrond Myklebust rcu_read_lock(); 41831be5bf1STrond Myklebust hlist_for_each_entry_rcu(entry, pos, &cache->hashtable[nr], cr_hash) { 41931be5bf1STrond Myklebust if (!entry->cr_ops->crmatch(acred, entry, flags)) 42031be5bf1STrond Myklebust continue; 4219499b434STrond Myklebust spin_lock(&cache->lock); 42231be5bf1STrond Myklebust if (test_bit(RPCAUTH_CRED_HASHED, &entry->cr_flags) == 0) { 4239499b434STrond Myklebust spin_unlock(&cache->lock); 42431be5bf1STrond Myklebust continue; 42531be5bf1STrond Myklebust } 42631be5bf1STrond Myklebust cred = get_rpccred(entry); 4279499b434STrond Myklebust spin_unlock(&cache->lock); 42831be5bf1STrond Myklebust break; 42931be5bf1STrond Myklebust } 43031be5bf1STrond Myklebust rcu_read_unlock(); 43131be5bf1STrond Myklebust 4329499b434STrond Myklebust if (cred != NULL) 43331be5bf1STrond Myklebust goto found; 43431be5bf1STrond Myklebust 43531be5bf1STrond Myklebust new = auth->au_ops->crcreate(auth, acred, flags); 43631be5bf1STrond Myklebust if (IS_ERR(new)) { 43731be5bf1STrond Myklebust cred = new; 43831be5bf1STrond Myklebust goto out; 43931be5bf1STrond Myklebust } 44031be5bf1STrond Myklebust 4419499b434STrond Myklebust spin_lock(&cache->lock); 442e092bdcdSTrond Myklebust hlist_for_each_entry(entry, pos, &cache->hashtable[nr], cr_hash) { 443e092bdcdSTrond Myklebust if (!entry->cr_ops->crmatch(acred, entry, flags)) 444e092bdcdSTrond Myklebust continue; 445e092bdcdSTrond Myklebust cred = get_rpccred(entry); 4461da177e4SLinus Torvalds break; 4471da177e4SLinus Torvalds } 44831be5bf1STrond Myklebust if (cred == NULL) { 44931be5bf1STrond Myklebust cred = new; 45031be5bf1STrond Myklebust set_bit(RPCAUTH_CRED_HASHED, &cred->cr_flags); 45131be5bf1STrond Myklebust hlist_add_head_rcu(&cred->cr_hash, &cache->hashtable[nr]); 45231be5bf1STrond Myklebust } else 453e092bdcdSTrond Myklebust list_add_tail(&new->cr_lru, &free); 4549499b434STrond Myklebust spin_unlock(&cache->lock); 45531be5bf1STrond Myklebust found: 456f64f9e71SJoe Perches if (test_bit(RPCAUTH_CRED_NEW, &cred->cr_flags) && 457f64f9e71SJoe Perches cred->cr_ops->cr_init != NULL && 458f64f9e71SJoe Perches !(flags & RPCAUTH_LOOKUP_NEW)) { 459fba3bad4STrond Myklebust int res = cred->cr_ops->cr_init(auth, cred); 460fba3bad4STrond Myklebust if (res < 0) { 461fba3bad4STrond Myklebust put_rpccred(cred); 462fba3bad4STrond Myklebust cred = ERR_PTR(res); 463fba3bad4STrond Myklebust } 4641da177e4SLinus Torvalds } 46531be5bf1STrond Myklebust rpcauth_destroy_credlist(&free); 46631be5bf1STrond Myklebust out: 46731be5bf1STrond Myklebust return cred; 4681da177e4SLinus Torvalds } 469e8914c65STrond Myklebust EXPORT_SYMBOL_GPL(rpcauth_lookup_credcache); 4701da177e4SLinus Torvalds 4711da177e4SLinus Torvalds struct rpc_cred * 4728a317760STrond Myklebust rpcauth_lookupcred(struct rpc_auth *auth, int flags) 4731da177e4SLinus Torvalds { 47486a264abSDavid Howells struct auth_cred acred; 4751da177e4SLinus Torvalds struct rpc_cred *ret; 47686a264abSDavid Howells const struct cred *cred = current_cred(); 4771da177e4SLinus Torvalds 4781da177e4SLinus Torvalds dprintk("RPC: looking up %s cred\n", 4791da177e4SLinus Torvalds auth->au_ops->au_name); 48086a264abSDavid Howells 48186a264abSDavid Howells memset(&acred, 0, sizeof(acred)); 48286a264abSDavid Howells acred.uid = cred->fsuid; 48386a264abSDavid Howells acred.gid = cred->fsgid; 48486a264abSDavid Howells acred.group_info = get_group_info(((struct cred *)cred)->group_info); 48586a264abSDavid Howells 4868a317760STrond Myklebust ret = auth->au_ops->lookup_cred(auth, &acred, flags); 4871da177e4SLinus Torvalds put_group_info(acred.group_info); 4881da177e4SLinus Torvalds return ret; 4891da177e4SLinus Torvalds } 4901da177e4SLinus Torvalds 4915fe4755eSTrond Myklebust void 4925fe4755eSTrond Myklebust rpcauth_init_cred(struct rpc_cred *cred, const struct auth_cred *acred, 4935fe4755eSTrond Myklebust struct rpc_auth *auth, const struct rpc_credops *ops) 4945fe4755eSTrond Myklebust { 4955fe4755eSTrond Myklebust INIT_HLIST_NODE(&cred->cr_hash); 496e092bdcdSTrond Myklebust INIT_LIST_HEAD(&cred->cr_lru); 4975fe4755eSTrond Myklebust atomic_set(&cred->cr_count, 1); 4985fe4755eSTrond Myklebust cred->cr_auth = auth; 4995fe4755eSTrond Myklebust cred->cr_ops = ops; 5005fe4755eSTrond Myklebust cred->cr_expire = jiffies; 5015fe4755eSTrond Myklebust #ifdef RPC_DEBUG 5025fe4755eSTrond Myklebust cred->cr_magic = RPCAUTH_CRED_MAGIC; 5035fe4755eSTrond Myklebust #endif 5045fe4755eSTrond Myklebust cred->cr_uid = acred->uid; 5055fe4755eSTrond Myklebust } 506e8914c65STrond Myklebust EXPORT_SYMBOL_GPL(rpcauth_init_cred); 5075fe4755eSTrond Myklebust 5088572b8e2STrond Myklebust struct rpc_cred * 5095d351754STrond Myklebust rpcauth_generic_bind_cred(struct rpc_task *task, struct rpc_cred *cred, int lookupflags) 5104ccda2cdSTrond Myklebust { 5114ccda2cdSTrond Myklebust dprintk("RPC: %5u holding %s cred %p\n", task->tk_pid, 5124ccda2cdSTrond Myklebust cred->cr_auth->au_ops->au_name, cred); 5138572b8e2STrond Myklebust return get_rpccred(cred); 5144ccda2cdSTrond Myklebust } 5155c691044STrond Myklebust EXPORT_SYMBOL_GPL(rpcauth_generic_bind_cred); 5164ccda2cdSTrond Myklebust 5178572b8e2STrond Myklebust static struct rpc_cred * 5185d351754STrond Myklebust rpcauth_bind_root_cred(struct rpc_task *task, int lookupflags) 5191da177e4SLinus Torvalds { 5201be27f36STrond Myklebust struct rpc_auth *auth = task->tk_client->cl_auth; 5211da177e4SLinus Torvalds struct auth_cred acred = { 522bf37f794SEric W. Biederman .uid = GLOBAL_ROOT_UID, 523bf37f794SEric W. Biederman .gid = GLOBAL_ROOT_GID, 5241da177e4SLinus Torvalds }; 5251da177e4SLinus Torvalds 52646121cf7SChuck Lever dprintk("RPC: %5u looking up %s cred\n", 5271be27f36STrond Myklebust task->tk_pid, task->tk_client->cl_auth->au_ops->au_name); 5288572b8e2STrond Myklebust return auth->au_ops->lookup_cred(auth, &acred, lookupflags); 529af093835STrond Myklebust } 530af093835STrond Myklebust 5318572b8e2STrond Myklebust static struct rpc_cred * 5325d351754STrond Myklebust rpcauth_bind_new_cred(struct rpc_task *task, int lookupflags) 533af093835STrond Myklebust { 534af093835STrond Myklebust struct rpc_auth *auth = task->tk_client->cl_auth; 535af093835STrond Myklebust 536af093835STrond Myklebust dprintk("RPC: %5u looking up %s cred\n", 537af093835STrond Myklebust task->tk_pid, auth->au_ops->au_name); 5388572b8e2STrond Myklebust return rpcauth_lookupcred(auth, lookupflags); 5391da177e4SLinus Torvalds } 5401da177e4SLinus Torvalds 541a17c2153STrond Myklebust static int 5424ccda2cdSTrond Myklebust rpcauth_bindcred(struct rpc_task *task, struct rpc_cred *cred, int flags) 5431da177e4SLinus Torvalds { 544a17c2153STrond Myklebust struct rpc_rqst *req = task->tk_rqstp; 5458572b8e2STrond Myklebust struct rpc_cred *new; 5465d351754STrond Myklebust int lookupflags = 0; 5475d351754STrond Myklebust 5485d351754STrond Myklebust if (flags & RPC_TASK_ASYNC) 5495d351754STrond Myklebust lookupflags |= RPCAUTH_LOOKUP_NEW; 5504ccda2cdSTrond Myklebust if (cred != NULL) 5518572b8e2STrond Myklebust new = cred->cr_ops->crbind(task, cred, lookupflags); 5524ccda2cdSTrond Myklebust else if (flags & RPC_TASK_ROOTCREDS) 5538572b8e2STrond Myklebust new = rpcauth_bind_root_cred(task, lookupflags); 5544ccda2cdSTrond Myklebust else 5558572b8e2STrond Myklebust new = rpcauth_bind_new_cred(task, lookupflags); 5568572b8e2STrond Myklebust if (IS_ERR(new)) 5578572b8e2STrond Myklebust return PTR_ERR(new); 558a17c2153STrond Myklebust if (req->rq_cred != NULL) 559a17c2153STrond Myklebust put_rpccred(req->rq_cred); 560a17c2153STrond Myklebust req->rq_cred = new; 5618572b8e2STrond Myklebust return 0; 5621da177e4SLinus Torvalds } 5631da177e4SLinus Torvalds 5641da177e4SLinus Torvalds void 5651da177e4SLinus Torvalds put_rpccred(struct rpc_cred *cred) 5661da177e4SLinus Torvalds { 567e092bdcdSTrond Myklebust /* Fast path for unhashed credentials */ 568f0380f3dSTrond Myklebust if (test_bit(RPCAUTH_CRED_HASHED, &cred->cr_flags) == 0) { 569f0380f3dSTrond Myklebust if (atomic_dec_and_test(&cred->cr_count)) 570f0380f3dSTrond Myklebust cred->cr_ops->crdestroy(cred); 5711da177e4SLinus Torvalds return; 572f0380f3dSTrond Myklebust } 573f0380f3dSTrond Myklebust 574e092bdcdSTrond Myklebust if (!atomic_dec_and_lock(&cred->cr_count, &rpc_credcache_lock)) 575e092bdcdSTrond Myklebust return; 576f5c2187cSTrond Myklebust if (!list_empty(&cred->cr_lru)) { 577f5c2187cSTrond Myklebust number_cred_unused--; 578e092bdcdSTrond Myklebust list_del_init(&cred->cr_lru); 579f5c2187cSTrond Myklebust } 5805f707eb4STrond Myklebust if (test_bit(RPCAUTH_CRED_HASHED, &cred->cr_flags) != 0) { 581f0380f3dSTrond Myklebust if (test_bit(RPCAUTH_CRED_UPTODATE, &cred->cr_flags) != 0) { 582e092bdcdSTrond Myklebust cred->cr_expire = jiffies; 583e092bdcdSTrond Myklebust list_add_tail(&cred->cr_lru, &cred_unused); 584f5c2187cSTrond Myklebust number_cred_unused++; 585f0380f3dSTrond Myklebust goto out_nodestroy; 586f0380f3dSTrond Myklebust } 587f0380f3dSTrond Myklebust if (!rpcauth_unhash_cred(cred)) { 588f0380f3dSTrond Myklebust /* We were hashed and someone looked us up... */ 589f0380f3dSTrond Myklebust goto out_nodestroy; 590f0380f3dSTrond Myklebust } 591e092bdcdSTrond Myklebust } 592e092bdcdSTrond Myklebust spin_unlock(&rpc_credcache_lock); 5931da177e4SLinus Torvalds cred->cr_ops->crdestroy(cred); 594f0380f3dSTrond Myklebust return; 595f0380f3dSTrond Myklebust out_nodestroy: 596f0380f3dSTrond Myklebust spin_unlock(&rpc_credcache_lock); 5971da177e4SLinus Torvalds } 598e8914c65STrond Myklebust EXPORT_SYMBOL_GPL(put_rpccred); 5991da177e4SLinus Torvalds 600d8ed029dSAlexey Dobriyan __be32 * 601d8ed029dSAlexey Dobriyan rpcauth_marshcred(struct rpc_task *task, __be32 *p) 6021da177e4SLinus Torvalds { 603a17c2153STrond Myklebust struct rpc_cred *cred = task->tk_rqstp->rq_cred; 6041da177e4SLinus Torvalds 60546121cf7SChuck Lever dprintk("RPC: %5u marshaling %s cred %p\n", 6061be27f36STrond Myklebust task->tk_pid, cred->cr_auth->au_ops->au_name, cred); 6070bbacc40SChuck Lever 6081da177e4SLinus Torvalds return cred->cr_ops->crmarshal(task, p); 6091da177e4SLinus Torvalds } 6101da177e4SLinus Torvalds 611d8ed029dSAlexey Dobriyan __be32 * 612d8ed029dSAlexey Dobriyan rpcauth_checkverf(struct rpc_task *task, __be32 *p) 6131da177e4SLinus Torvalds { 614a17c2153STrond Myklebust struct rpc_cred *cred = task->tk_rqstp->rq_cred; 6151da177e4SLinus Torvalds 61646121cf7SChuck Lever dprintk("RPC: %5u validating %s cred %p\n", 6171be27f36STrond Myklebust task->tk_pid, cred->cr_auth->au_ops->au_name, cred); 6180bbacc40SChuck Lever 6191da177e4SLinus Torvalds return cred->cr_ops->crvalidate(task, p); 6201da177e4SLinus Torvalds } 6211da177e4SLinus Torvalds 6229f06c719SChuck Lever static void rpcauth_wrap_req_encode(kxdreproc_t encode, struct rpc_rqst *rqstp, 6239f06c719SChuck Lever __be32 *data, void *obj) 6249f06c719SChuck Lever { 6259f06c719SChuck Lever struct xdr_stream xdr; 6269f06c719SChuck Lever 6279f06c719SChuck Lever xdr_init_encode(&xdr, &rqstp->rq_snd_buf, data); 6289f06c719SChuck Lever encode(rqstp, &xdr, obj); 6299f06c719SChuck Lever } 6309f06c719SChuck Lever 6311da177e4SLinus Torvalds int 6329f06c719SChuck Lever rpcauth_wrap_req(struct rpc_task *task, kxdreproc_t encode, void *rqstp, 633d8ed029dSAlexey Dobriyan __be32 *data, void *obj) 6341da177e4SLinus Torvalds { 635a17c2153STrond Myklebust struct rpc_cred *cred = task->tk_rqstp->rq_cred; 6361da177e4SLinus Torvalds 63746121cf7SChuck Lever dprintk("RPC: %5u using %s cred %p to wrap rpc data\n", 6381da177e4SLinus Torvalds task->tk_pid, cred->cr_ops->cr_name, cred); 6391da177e4SLinus Torvalds if (cred->cr_ops->crwrap_req) 6401da177e4SLinus Torvalds return cred->cr_ops->crwrap_req(task, encode, rqstp, data, obj); 6411da177e4SLinus Torvalds /* By default, we encode the arguments normally. */ 6429f06c719SChuck Lever rpcauth_wrap_req_encode(encode, rqstp, data, obj); 6439f06c719SChuck Lever return 0; 6441da177e4SLinus Torvalds } 6451da177e4SLinus Torvalds 646bf269551SChuck Lever static int 647bf269551SChuck Lever rpcauth_unwrap_req_decode(kxdrdproc_t decode, struct rpc_rqst *rqstp, 648bf269551SChuck Lever __be32 *data, void *obj) 649bf269551SChuck Lever { 650bf269551SChuck Lever struct xdr_stream xdr; 651bf269551SChuck Lever 652bf269551SChuck Lever xdr_init_decode(&xdr, &rqstp->rq_rcv_buf, data); 653bf269551SChuck Lever return decode(rqstp, &xdr, obj); 654bf269551SChuck Lever } 655bf269551SChuck Lever 6561da177e4SLinus Torvalds int 657bf269551SChuck Lever rpcauth_unwrap_resp(struct rpc_task *task, kxdrdproc_t decode, void *rqstp, 658d8ed029dSAlexey Dobriyan __be32 *data, void *obj) 6591da177e4SLinus Torvalds { 660a17c2153STrond Myklebust struct rpc_cred *cred = task->tk_rqstp->rq_cred; 6611da177e4SLinus Torvalds 66246121cf7SChuck Lever dprintk("RPC: %5u using %s cred %p to unwrap rpc data\n", 6631da177e4SLinus Torvalds task->tk_pid, cred->cr_ops->cr_name, cred); 6641da177e4SLinus Torvalds if (cred->cr_ops->crunwrap_resp) 6651da177e4SLinus Torvalds return cred->cr_ops->crunwrap_resp(task, decode, rqstp, 6661da177e4SLinus Torvalds data, obj); 6671da177e4SLinus Torvalds /* By default, we decode the arguments normally. */ 668bf269551SChuck Lever return rpcauth_unwrap_req_decode(decode, rqstp, data, obj); 6691da177e4SLinus Torvalds } 6701da177e4SLinus Torvalds 6711da177e4SLinus Torvalds int 6721da177e4SLinus Torvalds rpcauth_refreshcred(struct rpc_task *task) 6731da177e4SLinus Torvalds { 6749a84d380STrond Myklebust struct rpc_cred *cred; 6751da177e4SLinus Torvalds int err; 6761da177e4SLinus Torvalds 677a17c2153STrond Myklebust cred = task->tk_rqstp->rq_cred; 678a17c2153STrond Myklebust if (cred == NULL) { 679a17c2153STrond Myklebust err = rpcauth_bindcred(task, task->tk_msg.rpc_cred, task->tk_flags); 680a17c2153STrond Myklebust if (err < 0) 681a17c2153STrond Myklebust goto out; 682a17c2153STrond Myklebust cred = task->tk_rqstp->rq_cred; 683f81c6224SJoe Perches } 68446121cf7SChuck Lever dprintk("RPC: %5u refreshing %s cred %p\n", 6851be27f36STrond Myklebust task->tk_pid, cred->cr_auth->au_ops->au_name, cred); 6860bbacc40SChuck Lever 6871da177e4SLinus Torvalds err = cred->cr_ops->crrefresh(task); 688a17c2153STrond Myklebust out: 6891da177e4SLinus Torvalds if (err < 0) 6901da177e4SLinus Torvalds task->tk_status = err; 6911da177e4SLinus Torvalds return err; 6921da177e4SLinus Torvalds } 6931da177e4SLinus Torvalds 6941da177e4SLinus Torvalds void 6951da177e4SLinus Torvalds rpcauth_invalcred(struct rpc_task *task) 6961da177e4SLinus Torvalds { 697a17c2153STrond Myklebust struct rpc_cred *cred = task->tk_rqstp->rq_cred; 698fc432dd9STrond Myklebust 69946121cf7SChuck Lever dprintk("RPC: %5u invalidating %s cred %p\n", 7001be27f36STrond Myklebust task->tk_pid, cred->cr_auth->au_ops->au_name, cred); 701fc432dd9STrond Myklebust if (cred) 702fc432dd9STrond Myklebust clear_bit(RPCAUTH_CRED_UPTODATE, &cred->cr_flags); 7031da177e4SLinus Torvalds } 7041da177e4SLinus Torvalds 7051da177e4SLinus Torvalds int 7061da177e4SLinus Torvalds rpcauth_uptodatecred(struct rpc_task *task) 7071da177e4SLinus Torvalds { 708a17c2153STrond Myklebust struct rpc_cred *cred = task->tk_rqstp->rq_cred; 709fc432dd9STrond Myklebust 710fc432dd9STrond Myklebust return cred == NULL || 711fc432dd9STrond Myklebust test_bit(RPCAUTH_CRED_UPTODATE, &cred->cr_flags) != 0; 7121da177e4SLinus Torvalds } 713f5c2187cSTrond Myklebust 7148e1f936bSRusty Russell static struct shrinker rpc_cred_shrinker = { 7158e1f936bSRusty Russell .shrink = rpcauth_cache_shrinker, 7168e1f936bSRusty Russell .seeks = DEFAULT_SEEKS, 7178e1f936bSRusty Russell }; 718f5c2187cSTrond Myklebust 7195d8d9a4dSTrond Myklebust int __init rpcauth_init_module(void) 720f5c2187cSTrond Myklebust { 7215d8d9a4dSTrond Myklebust int err; 7225d8d9a4dSTrond Myklebust 7235d8d9a4dSTrond Myklebust err = rpc_init_authunix(); 7245d8d9a4dSTrond Myklebust if (err < 0) 7255d8d9a4dSTrond Myklebust goto out1; 7265d8d9a4dSTrond Myklebust err = rpc_init_generic_auth(); 7275d8d9a4dSTrond Myklebust if (err < 0) 7285d8d9a4dSTrond Myklebust goto out2; 7298e1f936bSRusty Russell register_shrinker(&rpc_cred_shrinker); 7305d8d9a4dSTrond Myklebust return 0; 7315d8d9a4dSTrond Myklebust out2: 7325d8d9a4dSTrond Myklebust rpc_destroy_authunix(); 7335d8d9a4dSTrond Myklebust out1: 7345d8d9a4dSTrond Myklebust return err; 735f5c2187cSTrond Myklebust } 736f5c2187cSTrond Myklebust 737c135e84aSStephen Rothwell void rpcauth_remove_module(void) 738f5c2187cSTrond Myklebust { 7395d8d9a4dSTrond Myklebust rpc_destroy_authunix(); 7405d8d9a4dSTrond Myklebust rpc_destroy_generic_auth(); 7418e1f936bSRusty Russell unregister_shrinker(&rpc_cred_shrinker); 742f5c2187cSTrond Myklebust } 743