From 7c478bd95313f5f23a4c958a745db2134aa03244 Mon Sep 17 00:00:00 2001 From: "stevel@tonic-gate" Date: Tue, 14 Jun 2005 00:00:00 -0700 Subject: OpenSolaris Launch --- usr/src/uts/common/fs/nfs/nfs_auth.c | 544 +++++++++++++++++++++++++++++++++++ 1 file changed, 544 insertions(+) create mode 100644 usr/src/uts/common/fs/nfs/nfs_auth.c (limited to 'usr/src/uts/common/fs/nfs/nfs_auth.c') diff --git a/usr/src/uts/common/fs/nfs/nfs_auth.c b/usr/src/uts/common/fs/nfs/nfs_auth.c new file mode 100644 index 0000000000..e4bf46d8e2 --- /dev/null +++ b/usr/src/uts/common/fs/nfs/nfs_auth.c @@ -0,0 +1,544 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (the "License"). You may not use this file except in compliance + * with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#define EQADDR(a1, a2) \ + (bcmp((char *)(a1)->buf, (char *)(a2)->buf, (a1)->len) == 0 && \ + (a1)->len == (a2)->len) + +static struct knetconfig auth_knconf; +static servinfo_t svp; +static clinfo_t ci; + +static struct kmem_cache *exi_cache_handle; +static void exi_cache_reclaim(void *); +static void exi_cache_trim(struct exportinfo *exi); + +int nfsauth_cache_hit; +int nfsauth_cache_miss; +int nfsauth_cache_reclaim; + +/* + * Number of seconds to wait for an NFSAUTH upcall. + */ +static int nfsauth_timeout = 20; + +void +nfsauth_init(void) +{ + vnode_t *kvp; + int error; + char addrbuf[SYS_NMLN+16]; + + /* + * Setup netconfig. + * Assume a connectionless loopback transport. + */ + if ((error = lookupname("/dev/ticotsord", UIO_SYSSPACE, FOLLOW, + NULLVPP, &kvp)) != 0) { + cmn_err(CE_CONT, "nfsauth: lookupname: %d\n", error); + return; + } + + auth_knconf.knc_rdev = kvp->v_rdev; + auth_knconf.knc_protofmly = NC_LOOPBACK; + auth_knconf.knc_semantics = NC_TPI_COTS_ORD; + VN_RELE(kvp); + + (void) strcpy(addrbuf, utsname.nodename); + (void) strcat(addrbuf, ".nfsauth"); + + svp.sv_knconf = &auth_knconf; + svp.sv_addr.buf = kmem_alloc(strlen(addrbuf)+1, KM_SLEEP); + (void) strcpy(svp.sv_addr.buf, addrbuf); + svp.sv_addr.len = (uint_t)strlen(addrbuf); + svp.sv_addr.maxlen = svp.sv_addr.len; + svp.sv_secdata = kmem_alloc(sizeof (struct sec_data), KM_SLEEP); + svp.sv_secdata->rpcflavor = AUTH_LOOPBACK; + svp.sv_secdata->data = NULL; + + ci.cl_prog = NFSAUTH_PROG; + ci.cl_vers = NFSAUTH_VERS; + ci.cl_readsize = 0; + ci.cl_retrans = 1; + ci.cl_flags = 0x0; + + /* + * Allocate nfsauth cache handle + */ + exi_cache_handle = kmem_cache_create("exi_cache_handle", + sizeof (struct auth_cache), 0, NULL, NULL, + exi_cache_reclaim, NULL, NULL, 0); +} + +/* + * Finalization routine for nfsauth. It is important to call this routine + * before destroying the exported_lock. + */ +void +nfsauth_fini(void) +{ + /* + * Deallocate nfsauth cache handle + */ + kmem_cache_destroy(exi_cache_handle); +} + +static int +nfsauth_clget(CLIENT **newcl, struct chtab **chp) +{ + return (clget(&ci, &svp, CRED(), newcl, chp)); +} + +/* + * Convert the address in a netbuf to + * a hash index for the auth_cache table. + */ +static int +hash(struct netbuf *a) +{ + int i, h = 0; + + for (i = 0; i < a->len; i++) + h ^= a->buf[i]; + + return (h & (AUTH_TABLESIZE - 1)); +} + +/* + * Mask out the components of an + * address that do not identify + * a host. For socket addresses the + * masking gets rid of the port number. + */ +static void +addrmask(struct netbuf *addr, struct netbuf *mask) +{ + int i; + + for (i = 0; i < addr->len; i++) + addr->buf[i] &= mask->buf[i]; +} + +/* + * nfsauth4_access is used for NFS V4 auth checking. Besides doing + * the common nfsauth_access(), it will check if the client can + * have a limited access to this vnode even if the security flavor + * used does not meet the policy. + */ +int +nfsauth4_access(struct exportinfo *exi, vnode_t *vp, struct svc_req *req) +{ + int access; + + access = nfsauth_access(exi, req); + + /* + * There are cases that the server needs to allow the client + * to have a limited view. + * + * e.g. + * /export is shared as "sec=sys,rw=dfs-test-4,sec=krb5,rw" + * /export/home is shared as "sec=sys,rw" + * + * When the client mounts /export with sec=sys, the client + * would get a limited view with RO access on /export to see + * "home" only because the client is allowed to access + * /export/home with auth_sys. + */ + if (access & NFSAUTH_DENIED || access & NFSAUTH_WRONGSEC) { + /* + * Allow ro permission with LIMITED view if there is a + * sub-dir exported under vp. + */ + if (has_visible(exi, vp)) { + return (NFSAUTH_LIMITED); + } + } + + return (access); +} + +/* + * Get the access information from the cache or callup to the mountd + * to get and cache the access information in the kernel. + */ +int +nfsauth_cache_get(struct exportinfo *exi, struct svc_req *req, int flavor) +{ + struct netbuf addr, *claddr; + struct auth_cache **head, *ap; + CLIENT *clnt; + struct chtab *ch; + struct auth_req request; + struct auth_res result; + enum clnt_stat rpcstat; + int access; + struct timeval timout; + static time_t exi_msg = 0; + time_t now; + + /* + * Now check whether this client already + * has an entry for this flavor in the cache + * for this export. + * Get the caller's address, mask off the + * parts of the address that do not identify + * the host (port number, etc), and then hash + * it to find the chain of cache entries. + */ + + claddr = svc_getrpccaller(req->rq_xprt); + addr = *claddr; + addr.buf = mem_alloc(addr.len); + bcopy(claddr->buf, addr.buf, claddr->len); + addrmask(&addr, svc_getaddrmask(req->rq_xprt)); + head = &exi->exi_cache[hash(&addr)]; + + rw_enter(&exi->exi_cache_lock, RW_READER); + for (ap = *head; ap; ap = ap->auth_next) { + if (EQADDR(&addr, &ap->auth_addr) && flavor == ap->auth_flavor) + break; + } + if (ap) { /* cache hit */ + access = ap->auth_access; + ap->auth_time = gethrestime_sec(); + nfsauth_cache_hit++; + } + + rw_exit(&exi->exi_cache_lock); + + if (ap) { + kmem_free(addr.buf, addr.len); + return (access); + } + + nfsauth_cache_miss++; + + /* + * No entry in the cache for this client/flavor + * so we need to call the nfsauth service in the + * mount daemon. + */ + + if (nfsauth_clget(&clnt, &ch)) { + kmem_free(addr.buf, addr.len); + return (NFSAUTH_DROP); + } + + timout.tv_sec = nfsauth_timeout; + timout.tv_usec = 0; + + request.req_client.n_len = addr.len; + request.req_client.n_bytes = addr.buf; + request.req_netid = svc_getnetid(req->rq_xprt); + request.req_path = exi->exi_export.ex_path; + request.req_flavor = flavor; + + rpcstat = clnt_call(clnt, NFSAUTH_ACCESS, + (xdrproc_t)xdr_auth_req, (caddr_t)&request, + (xdrproc_t)xdr_auth_res, (caddr_t)&result, + timout); + + switch (rpcstat) { + case RPC_SUCCESS: + access = result.auth_perm; + break; + case RPC_INTR: + break; + case RPC_TIMEDOUT: + /* + * Show messages no more than once per minute + */ + now = gethrestime_sec(); + if ((exi_msg + 60) < now) { + exi_msg = now; + cmn_err(CE_WARN, "nfsauth: mountd not responding"); + } + break; + default: + /* + * Show messages no more than once per minute + */ + now = gethrestime_sec(); + if ((exi_msg + 60) < now) { + char *errmsg; + + exi_msg = now; + errmsg = clnt_sperror(clnt, "nfsauth upcall failed"); + cmn_err(CE_WARN, errmsg); + kmem_free(errmsg, MAXPATHLEN); + } + break; + } + + clfree(clnt, ch); + if (rpcstat != RPC_SUCCESS) { + kmem_free(addr.buf, addr.len); + return (NFSAUTH_DROP); + } + + /* + * Now cache the result on the cache chain + * for this export (if there's enough memory) + */ + ap = kmem_cache_alloc(exi_cache_handle, KM_NOSLEEP); + if (ap) { + ap->auth_addr = addr; + ap->auth_flavor = flavor; + ap->auth_access = access; + ap->auth_time = gethrestime_sec(); + rw_enter(&exi->exi_cache_lock, RW_WRITER); + ap->auth_next = *head; + *head = ap; + rw_exit(&exi->exi_cache_lock); + } else { + kmem_free(addr.buf, addr.len); + } + + return (access); +} + +/* + * Check if the requesting client has access to the filesystem with + * a given nfs flavor number which is an explicitly shared flavor. + */ +int +nfsauth4_secinfo_access(struct exportinfo *exi, struct svc_req *req, + int flavor, int perm) +{ + int access; + + if (! (perm & M_4SEC_EXPORTED)) { + return (NFSAUTH_DENIED); + } + + /* + * Optimize if there are no lists + */ + if ((perm & M_ROOT) == 0) { + perm &= ~M_4SEC_EXPORTED; + if (perm == M_RO) + return (NFSAUTH_RO); + if (perm == M_RW) + return (NFSAUTH_RW); + } + + access = nfsauth_cache_get(exi, req, flavor); + + return (access); +} + +int +nfsauth_access(struct exportinfo *exi, struct svc_req *req) +{ + int access, mapaccess; + struct secinfo *sp; + int i, flavor, perm; + int authnone_entry = -1; + + /* + * Get the nfs flavor number from xprt. + */ + flavor = (int)(uintptr_t)req->rq_xprt->xp_cookie; + + /* + * First check the access restrictions on the filesystem. If + * there are no lists associated with this flavor then there's no + * need to make an expensive call to the nfsauth service or to + * cache anything. + */ + + sp = exi->exi_export.ex_secinfo; + for (i = 0; i < exi->exi_export.ex_seccnt; i++) { + if (flavor != sp[i].s_secinfo.sc_nfsnum) { + if (sp[i].s_secinfo.sc_nfsnum == AUTH_NONE) + authnone_entry = i; + continue; + } + break; + } + + mapaccess = 0; + + if (i >= exi->exi_export.ex_seccnt) { + /* + * Flavor not found, but use AUTH_NONE if it exists + */ + if (authnone_entry == -1) + return (NFSAUTH_DENIED); + flavor = AUTH_NONE; + mapaccess = NFSAUTH_MAPNONE; + i = authnone_entry; + } + + /* + * If the flavor is in the ex_secinfo list, but not an explicitly + * shared flavor by the user, it is a result of the nfsv4 server + * namespace setup. We will grant an RO permission similar for + * a pseudo node except that this node is a shared one. + * + * e.g. flavor in (flavor) indicates that it is not explictly + * shared by the user: + * + * / (sys, krb5) + * | + * export #share -o sec=sys (krb5) + * | + * secure #share -o sec=krb5 + * + * In this case, when a krb5 request coming in to access + * /export, RO permission is granted. + */ + if (!(sp[i].s_flags & M_4SEC_EXPORTED)) + return (mapaccess | NFSAUTH_RO); + + /* + * Optimize if there are no lists + */ + perm = sp[i].s_flags; + if ((perm & M_ROOT) == 0) { + perm &= ~M_4SEC_EXPORTED; + if (perm == M_RO) + return (mapaccess | NFSAUTH_RO); + if (perm == M_RW) + return (mapaccess | NFSAUTH_RW); + } + + access = nfsauth_cache_get(exi, req, flavor); + + return (access | mapaccess); +} + +/* + * Free the nfsauth cache for a given export + */ +void +nfsauth_cache_free(struct exportinfo *exi) +{ + int i; + struct auth_cache *p, *next; + + for (i = 0; i < AUTH_TABLESIZE; i++) { + for (p = exi->exi_cache[i]; p; p = next) { + kmem_free(p->auth_addr.buf, p->auth_addr.len); + next = p->auth_next; + kmem_cache_free(exi_cache_handle, (void *)p); + } + } +} + +/* + * Called by the kernel memory allocator when + * memory is low. Free unused cache entries. + * If that's not enough, the VM system will + * call again for some more. + */ +/*ARGSUSED*/ +void +exi_cache_reclaim(void *cdrarg) +{ + int i; + struct exportinfo *exi; + + rw_enter(&exported_lock, RW_READER); + + for (i = 0; i < EXPTABLESIZE; i++) { + for (exi = exptable[i]; exi; exi = exi->exi_hash) { + exi_cache_trim(exi); + } + } + nfsauth_cache_reclaim++; + + rw_exit(&exported_lock); +} + +/* + * Don't reclaim entries until they've been + * in the cache for at least exi_cache_time + * seconds. + */ +time_t exi_cache_time = 60 * 60; + +void +exi_cache_trim(struct exportinfo *exi) +{ + struct auth_cache *p; + struct auth_cache *prev, *next; + int i; + time_t stale_time; + + stale_time = gethrestime_sec() - exi_cache_time; + + rw_enter(&exi->exi_cache_lock, RW_WRITER); + + for (i = 0; i < AUTH_TABLESIZE; i++) { + + /* + * Free entries that have not been + * used for exi_cache_time seconds. + */ + prev = NULL; + for (p = exi->exi_cache[i]; p; p = next) { + next = p->auth_next; + if (p->auth_time > stale_time) { + prev = p; + continue; + } + + kmem_free(p->auth_addr.buf, p->auth_addr.len); + kmem_cache_free(exi_cache_handle, (void *)p); + if (prev == NULL) + exi->exi_cache[i] = next; + else + prev->auth_next = next; + } + } + + rw_exit(&exi->exi_cache_lock); +} -- cgit v1.2.3