/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* NFS Version 4 client side SECINFO code.
*/
#include <nfs/nfs4_clnt.h>
#include <nfs/nfs4.h>
#include <nfs/nfs_clnt.h>
#include <nfs/rnode4.h>
#include <sys/cmn_err.h>
#include <sys/cred.h>
#include <sys/systm.h>
/*
* Set up the security flavors supported in this release.
* In the order of potential usage.
*/
#define SECINFO_SUPPORT_COUNT 6 /* sys, krb5, krb5i, krb5p, none, dh */
static char krb5_val[] = {'\x2A', '\x86', '\x48', '\x86', '\xF7', \
'\x12', '\x01', '\x02', '\x02'};
static sec_oid4 krb5_oid = {9, krb5_val};
static SECINFO4res *secinfo_support;
/* XXX should come from auth.h, do the cleanup someday */
extern void sec_clnt_freeinfo(struct sec_data *);
/*
* "nfsstat -m" needs to print out what flavor is used for a mount
* point. V3 kernel gets the nfs pseudo flavor from the userland and provides
* nfsstat with such information. However, in V4, we do not have nfs pseudo
* flavors mapping in the kernel for the rpcsec_gss data negotiated from
* the nfs server.
*
* XXX
* Hard coded the mapping in V4 for now. We should look into a possibility
* to return the rpcsec_gss mechanism and service information to nfsstat and
* perhaps have nfsstat print out the mech and service seperately...
*
* We should avoid referring to nfssec.conf file in V4. The original reason
* for having /etc/nfssec.conf file is because V3 MOUNT protocol can only
* return an integer for a flavor, thus the term "nfs pseudo flavor" is
* defined and the nfssec.conf file is used to map the nfs pseudo flavor
* to rpcsec_gss data (mech, service, default-qop). Now, V4 can return the
* rpcsec_gss data instead of an integer, so in theory, V4 should not need
* to depend on the nfssec.conf file anymore.
*/
#define NFS_FLAVOR_KRB5 390003
#define NFS_FLAVOR_KRB5I 390004
#define NFS_FLAVOR_KRB5P 390005
/*
* Currently, 6 flavors are supported: sys, krb5, krb5i, krb5p, dh, none.
* Without proper keys, krb5* or dh will fail.
*
* XXX kgss_indicate_mechs() should be able to tell us what gss mechanisms
* are supported on this host (/etc/gss/mech), thus nfs should be able to
* use them. However, the dh640 and dh1024 implementation are not nfs tested.
* Should look into using kgss_indicate_mechs when new gss mechanism is added.
*/
void
nfs4_secinfo_init(void)
{
secinfo4 *val;
int i;
secinfo_support = kmem_alloc(sizeof (SECINFO4res), KM_SLEEP);
secinfo_support->SECINFO4resok_len = SECINFO_SUPPORT_COUNT;
val = kmem_alloc(
secinfo_support->SECINFO4resok_len * sizeof (secinfo4),
KM_SLEEP);
val[0].flavor = AUTH_SYS;
val[0].flavor_info.oid.sec_oid4_len = 0;
val[0].flavor_info.oid.sec_oid4_val = NULL;
val[0].flavor_info.service = 0;
val[0].flavor_info.qop = 0;
/* add krb5, krb5i, krb5p */
for (i = 1; i <= 3; i++) {
val[i].flavor = RPCSEC_GSS;
val[i].flavor_info.oid = krb5_oid; /* struct copy */
val[i].flavor_info.service = i;
val[i].flavor_info.qop = 0;
}
val[4].flavor = AUTH_DH;
val[4].flavor_info.oid.sec_oid4_len = 0;
val[4].flavor_info.oid.sec_oid4_val = NULL;
val[4].flavor_info.service = 0;
val[4].flavor_info.qop = 0;
val[5].flavor = AUTH_NONE;
val[5].flavor_info.oid.sec_oid4_len = 0;
val[5].flavor_info.oid.sec_oid4_val = NULL;
val[5].flavor_info.service = 0;
val[5].flavor_info.qop = 0;
#if !defined(lint)
ASSERT(SECINFO_SUPPORT_COUNT == 6);
#endif
secinfo_support->SECINFO4resok_val = val;
}
/*
* clean up secinfo_support
*/
void
nfs4_secinfo_fini(void)
{
kmem_free(secinfo_support->SECINFO4resok_val,
secinfo_support->SECINFO4resok_len * sizeof (secinfo4));
kmem_free(secinfo_support, sizeof (SECINFO4res));
}
/*
* Map RPCSEC_GSS data to a nfs pseudo flavor number defined
* in the nfssec.conf file.
*
* mechanism service qop nfs-pseudo-flavor
* ----------------------------------------------------
* kerberos_v5 none default 390003/krb5
* kerberos_v5 integrity default 390004/krb5i
* kerberos_v5 privacy default 390005/krb5p
*
* XXX need to re-visit the mapping semantics when a new
* security mechanism is to be added.
*/
int
secinfo2nfsflavor(sec_oid4 *mech_oid, rpc_gss_svc_t service)
{
/* Is this kerberos_v5? */
if (bcmp(mech_oid->sec_oid4_val, krb5_oid.sec_oid4_val,
krb5_oid.sec_oid4_len) != 0) {
return (0);
}
/* for krb5, krb5i, krb5p mapping */
switch (service) {
case RPC_GSS_SVC_NONE:
return (NFS_FLAVOR_KRB5);
case RPC_GSS_SVC_INTEGRITY:
return (NFS_FLAVOR_KRB5I);
case RPC_GSS_SVC_PRIVACY:
return (NFS_FLAVOR_KRB5P);
default:
break;
}
/* no mapping */
return (0);
}
/*
* secinfo_create() maps the secinfo4 data coming over the wire
* to sv_secinfo data structure in servinfo4_t
*/
static sv_secinfo_t *
secinfo_create(servinfo4_t *svp, SECINFO4res *sec_info, char *servname)
{
uint_t i, seccnt, scnt;
sec_data_t *sdata;
sv_secinfo_t *sinfo;
uint_t len = sec_info->SECINFO4resok_len;
secinfo4 *value = sec_info->SECINFO4resok_val;
if (len == 0)
return (NULL);
seccnt = len;
/*
* If there is no valid sv_dhsec data available but an AUTH_DH
* is in the list, skip AUTH_DH flavor.
*/
if (!svp->sv_dhsec) {
for (i = 0; i < len; i++) {
if (value[i].flavor == AUTH_DH)
seccnt--;
}
}
if (seccnt == 0)
return (NULL);
sdata = kmem_alloc(sizeof (sec_data_t) * seccnt, KM_SLEEP);
scnt = 0;
for (i = 0; i < len; i++) {
secinfo4 *val = &value[i];
gss_clntdata_t *data;
rpcsec_gss_info *info;
sdata[scnt].flags = 0;
sdata[scnt].rpcflavor = val->flavor;
switch (val->flavor) {
case RPCSEC_GSS:
data = kmem_alloc(sizeof (gss_clntdata_t), KM_SLEEP);
data->realm[0] = '\0';
info = &val->flavor_info;
data->service = (rpc_gss_service_t)info->service;
data->qop = (uint_t)info->qop;
data->mechanism.length = info->oid.sec_oid4_len;
data->mechanism.elements =
kmem_alloc(info->oid.sec_oid4_len, KM_SLEEP);
bcopy(info->oid.sec_oid4_val,
data->mechanism.elements, info->oid.sec_oid4_len);
data->uname[0] = 'n'; data->uname[1] = 'f';
data->uname[2] = 's'; data->uname[3] = '\0';
(void) strcpy(data->inst, servname);
sdata[scnt].data = (caddr_t)data;
sdata[scnt].secmod =
secinfo2nfsflavor(&info->oid, info->service);
scnt++;
break;
case AUTH_DH:
if (svp->sv_dhsec) {
sdata[scnt] = *svp->sv_dhsec;
scnt++;
break;
}
/* no auth_dh data on the client, skip auth_dh */
continue;
default:
sdata[scnt].secmod = val->flavor;
sdata[scnt].data = NULL;
scnt++;
break;
}
}
ASSERT(seccnt == scnt);
sinfo = kmem_alloc(sizeof (sv_secinfo_t), KM_SLEEP);
sinfo->count = seccnt;
sinfo->sdata = sdata;
return (sinfo);
}
/*
* secinfo_free() frees the malloc'd portion of a sv_secinfo_t in servinfo4_t.
*
* This is similar to sec_clnt_freeinfo() offered from rpcsec module,
* except that sec_clnt_freeinfo() frees up an individual secdata.
*/
void
secinfo_free(sv_secinfo_t *secinfo)
{
int i;
if (secinfo == NULL)
return;
for (i = 0; i < secinfo->count; i++) {
if (secinfo->sdata[i].rpcflavor == RPCSEC_GSS) {
gss_clntdata_t *data = (gss_clntdata_t *)
secinfo->sdata[i].data;
/*
* An auth handle may already cached in rpcsec_gss
* module per this secdata. Purge the cache entry
* before freeing up this secdata. Can't use
* sec_clnt_freeinfo since the allocation of secinfo
* is different from sec_data.
*/
(void) rpc_gss_secpurge((void *)&secinfo->sdata[i]);
kmem_free(data->mechanism.elements,
data->mechanism.length);
kmem_free(data, sizeof (gss_clntdata_t));
}
if (secinfo->sdata[i].rpcflavor == AUTH_DH) {
/* release ref to sv_dhsec */
secinfo->sdata[i].data = NULL;
/*
* No need to purge the auth_dh cache entry (e.g. call
* purge_authtab()) since the AUTH_DH data used here
* are always the same.
*/
}
}
kmem_free(secinfo->sdata, sizeof (sec_data_t) * secinfo->count);
kmem_free(secinfo, sizeof (sv_secinfo_t));
}
/*
* Check if there is more secinfo to try.
* If TRUE, try again.
*/
static bool_t
secinfo_check(servinfo4_t *svp)
{
(void) nfs_rw_enter_sig(&svp->sv_lock, RW_WRITER, 0);
if (svp->sv_secinfo == NULL) {
nfs_rw_exit(&svp->sv_lock);
return (FALSE);
}
svp->sv_secinfo->index++;
if (svp->sv_secinfo->index < svp->sv_secinfo->count) {
svp->sv_flags |= SV4_TRYSECINFO;
svp->sv_currsec =
&svp->sv_secinfo->sdata[svp->sv_secinfo->index];
nfs_rw_exit(&svp->sv_lock);
return (TRUE);
} else {
svp->sv_secinfo->index = 0;
svp->sv_flags &= ~SV4_TRYSECINFO;
svp->sv_currsec = NULL;
nfs_rw_exit(&svp->sv_lock);
return (FALSE);
}
}
/*
* Update the secinfo related fields in svp.
*
* secinfo_update will free the previous sv_secinfo and update with
* the new secinfo. However, if the sv_secinfo is saved into sv_save_secinfo
* before the recovery starts via save_mnt_secinfo(), sv_secinfo will not
* be freed until the recovery is done.
*/
static void
secinfo_update(servinfo4_t *svp, SECINFO4res *sec_info)
{
sv_secinfo_t *newsecinfo;
/*
* Create secinfo before freeing the old one to make sure
* they are not using the same address.
*/
newsecinfo = secinfo_create(svp, sec_info, svp->sv_hostname);
(void) nfs_rw_enter_sig(&svp->sv_lock, RW_WRITER, 0);
if (svp->sv_secinfo && svp->sv_secinfo != svp->sv_save_secinfo) {
secinfo_free(svp->sv_secinfo);
}
svp->sv_secinfo = newsecinfo;
if (svp->sv_secinfo) {
svp->sv_secinfo->index = 0;
svp->sv_flags |= SV4_TRYSECINFO;
svp->sv_currsec =
&svp->sv_secinfo->sdata[svp->sv_secinfo->index];
} else {
svp->sv_flags &= ~SV4_TRYSECINFO;
svp->sv_currsec = NULL;
}
nfs_rw_exit(&svp->sv_lock);
}
/*
* Save the original mount point security information.
*
* sv_savesec saves the pointer of sv_currsec which points to one of the
* secinfo data in the sv_secinfo list. i.e. sv_currsec == &sv_secinfo[index].
*
* sv_save_secinfo saves the pointer of sv_secinfo which is the list of
* secinfo data returned by the server.
*/
void
save_mnt_secinfo(servinfo4_t *svp)
{
(void) nfs_rw_enter_sig(&svp->sv_lock, RW_WRITER, 0);
if (svp->sv_currsec) {
svp->sv_savesec = svp->sv_currsec;
svp->sv_save_secinfo = svp->sv_secinfo;
} else {
ASSERT(svp->sv_save_secinfo == NULL);
svp->sv_savesec = svp->sv_secdata;
}
nfs_rw_exit(&svp->sv_lock);
}
/*
* Check if we need to restore what is saved in sv_savesec and sv_save_secinfo
* to be the current secinfo information - sv_currsec and sv_secinfo.
*
* If op a node that is a stub for a crossed mount point,
* keep the original secinfo flavor for the current file system,
* not the crossed one.
*/
void
check_mnt_secinfo(servinfo4_t *svp, vnode_t *vp)
{
bool_t is_restore;
(void) nfs_rw_enter_sig(&svp->sv_lock, RW_WRITER, 0);
is_restore = (vp == NULL || (RP_ISSTUB(VTOR4(vp)))) &&
svp->sv_save_secinfo &&
(svp->sv_secinfo != svp->sv_save_secinfo);
if (is_restore) {
secinfo_free(svp->sv_secinfo);
if (svp->sv_savesec == svp->sv_secdata) {
ASSERT(svp->sv_save_secinfo == NULL);
svp->sv_secinfo = NULL;
svp->sv_currsec = NULL;
} else {
ASSERT(svp->sv_save_secinfo != NULL);
svp->sv_secinfo = svp->sv_save_secinfo;
svp->sv_currsec = svp->sv_savesec;
}
} else {
if (svp->sv_save_secinfo &&
svp->sv_save_secinfo != svp->sv_secinfo)
secinfo_free(svp->sv_save_secinfo);
}
svp->sv_save_secinfo = NULL;
svp->sv_savesec = NULL;
nfs_rw_exit(&svp->sv_lock);
}
/*
* Use the security flavors supported on the client to try
* PUTROOTFH until a flavor is found.
*
* PUTROOTFH could return NFS4ERR_RESOURCE and NFS4ERR_WRONGSEC that
* may need a recovery action. This routine only handles NFS4ERR_WRONGSEC.
* For other recovery action, it returns ok to the caller for retry.
*/
static int
secinfo_tryroot_otw(mntinfo4_t *mi, cred_t *cr)
{
COMPOUND4args_clnt args;
COMPOUND4res_clnt res;
nfs_argop4 argop;
int doqueue = 1;
bool_t needrecov = FALSE;
nfs4_error_t e = { 0, NFS4_OK, RPC_SUCCESS };
/* use the flavors supported on the client */
secinfo_update(mi->mi_curr_serv, secinfo_support);
/* Compound {Putroofh} */
args.ctag = TAG_PUTROOTFH;
args.array_len = 1;
args.array = &argop;
argop.argop = OP_PUTROOTFH;
retry:
NFS4_DEBUG(nfs4_client_call_debug, (CE_NOTE,
"secinfo_tryroot_otw: %s call, mi 0x%p",
needrecov ? "recov" : "first", (void*)mi));
rfs4call(mi, &args, &res, cr, &doqueue, RFSCALL_SOFT, &e);
needrecov = nfs4_needs_recovery(&e, FALSE, mi->mi_vfsp);
if (e.error && !needrecov) {
return (e.error);
}
if (res.status == NFS4ERR_WRONGSEC) {
(void) xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
if (secinfo_check(mi->mi_curr_serv))
goto retry;
/*
* Have tried all flavors supported on the client,
* but still get NFS4ERR_WRONGSEC. Nothing more can
* be done.
*/
return (geterrno4(res.status));
}
if (needrecov) {
NFS4_DEBUG(nfs4_client_recov_debug, (CE_NOTE,
"secinfo_tryroot_otw: let the caller retry\n"));
if (!e.error)
(void) xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
return (0);
}
if (res.status) {
(void) xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
return (geterrno4(res.status));
}
/*
* Done.
*
* Now, mi->sv_curr_server->sv_currsec points to the flavor found.
* SV4_TRYSECINFO has been cleared in rfs4call.
* sv_currsec will be used.
*/
(void) xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
return (e.error);
}
/*
* Caculate the total number of components within a given pathname.
* Assuming the given pathname is not null.
* e.g. returns 5 for "/a/b/c/d/e" or "a/b/c/d/e"
* returns 0 for "/"
*/
static int
comp_total(char *inpath)
{
int tnum = 0;
char *slash;
while (*inpath != '\0') {
if (*inpath == '/') {
inpath++;
continue;
}
if ((slash = (char *)strchr(inpath, '/')) == NULL) {
tnum++;
break;
} else {
tnum++;
inpath = slash + 1;
}
}
return (tnum);
}
/*
* Get the pointer of the n-th component in the given path.
* Mark the preceeding '/' of the component to be '\0' when done.
* Assuming nth is > 0.
*/
static void
comp_getn(char *inpath, int nth, component4 *comp)
{
char *path = inpath, *comp_start, *slash = NULL;
int count = 0;
while ((count != nth) && (*path != '\0')) {
comp_start = path;
/* ignore slashes prior to the component name */
while (*path == '/')
path++;
if (*path != '\0') {
comp_start = path;
count++;
}
if ((slash = strchr(path, '/')) == NULL)
break;
else
path = slash + 1;
}
if (count == nth) {
if (slash)
*slash = '\0';
comp->utf8string_len = strlen(comp_start);
comp->utf8string_val = comp_start;
if (comp_start != inpath) {
comp_start--;
*comp_start = '\0';
}
} else {
comp->utf8string_len = 0;
comp->utf8string_val = NULL;
}
}
/*
* SECINFO over the wire compound operation
*
* compound {PUTROOTFH, {LOOKUP parent-path}, SECINFO component}
*
* This routine assumes there is a component to work on, thus the
* given pathname (svp->sv_path) has to have at least 1 component.
*
* isrecov - TRUE if this routine is called from a recovery thread.
*
* nfs4secinfo_otw() only deals with NFS4ERR_WRONGSEC recovery. If this
* is already in a recovery thread, then setup the non-wrongsec recovery
* action thru nfs4_start_recovery and return to the outer loop in
* nfs4_recov_thread() for recovery. If this is not called from a recovery
* thread, then error out and let the caller decide what to do.
*/
static int
nfs4secinfo_otw(mntinfo4_t *mi, cred_t *cr, servinfo4_t *svp, int isrecov)
{
COMPOUND4args_clnt args;
COMPOUND4res_clnt res;
nfs_argop4 *argop;
nfs_resop4 *resop;
lookup4_param_t lookuparg;
uint_t path_len;
int doqueue;
int numops, num_argops;
char *tmp_path;
component4 comp;
uint_t ncomp, tcomp;
bool_t needrecov = FALSE;
nfs4_error_t e = { 0, NFS4_OK, RPC_SUCCESS };
(void) nfs_rw_enter_sig(&svp->sv_lock, RW_READER, 0);
ncomp = tcomp = comp_total(svp->sv_path);
path_len = strlen(svp->sv_path);
nfs_rw_exit(&svp->sv_lock);
ASSERT(ncomp > 0);
retry:
tmp_path = kmem_alloc(path_len + 1, KM_SLEEP);
(void) nfs_rw_enter_sig(&svp->sv_lock, RW_READER, 0);
bcopy(svp->sv_path, tmp_path, path_len + 1);
nfs_rw_exit(&svp->sv_lock);
comp_getn(tmp_path, ncomp, &comp);
args.ctag = TAG_SECINFO;
lookuparg.l4_getattrs = LKP4_NO_ATTRIBUTES;
lookuparg.argsp = &args;
lookuparg.resp = &res;
lookuparg.header_len = 1; /* Putrootfh */
lookuparg.trailer_len = 1; /* Secinfo */
lookuparg.ga_bits = NULL;
lookuparg.mi = mi;
/* setup LOOKUPs for parent path */
(void) nfs4lookup_setup(tmp_path, &lookuparg, 0);
argop = args.array;
/* put root fh */
argop[0].argop = OP_PUTROOTFH;
/* setup SECINFO op */
num_argops = args.array_len;
argop[num_argops - 1].argop = OP_SECINFO;
argop[num_argops - 1].nfs_argop4_u.opsecinfo.name.utf8string_len =
comp.utf8string_len;
argop[num_argops - 1].nfs_argop4_u.opsecinfo.name.utf8string_val =
comp.utf8string_val;
doqueue = 1;
NFS4_DEBUG(nfs4_client_call_debug, (CE_NOTE,
"nfs4secinfo_otw: %s call, mi 0x%p",
needrecov ? "recov" : "first", (void*)mi));
rfs4call(mi, &args, &res, cr, &doqueue, RFSCALL_SOFT, &e);
needrecov = nfs4_needs_recovery(&e, FALSE, mi->mi_vfsp);
if (e.error && !needrecov) {
nfs4args_lookup_free(argop, num_argops);
kmem_free(argop, lookuparg.arglen * sizeof (nfs_argop4));
kmem_free(tmp_path, path_len + 1);
return (e.error);
}
/*
* Secinfo compound op may fail with NFS4ERR_WRONGSEC from
* PUTROOTFH or LOOKUP. Special handling here to recover it.
*/
if (res.status == NFS4ERR_WRONGSEC) {
if (res.array_len == 1) {
/*
* If a flavor can not be found via trying
* all supported flavors on the client, no
* more operations.
*/
ncomp = tcomp;
nfs4args_lookup_free(argop, num_argops);
kmem_free(argop,
lookuparg.arglen * sizeof (nfs_argop4));
(void) xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
kmem_free(tmp_path, path_len + 1);
if (e.error = secinfo_tryroot_otw(mi, cr)) {
return (e.error);
}
goto retry;
}
ncomp = res.array_len - 1;
nfs4args_lookup_free(argop, num_argops);
kmem_free(argop, lookuparg.arglen * sizeof (nfs_argop4));
(void) xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
kmem_free(tmp_path, path_len + 1);
goto retry;
}
/*
* This routine does not do recovery for non NFS4ERR_WRONGSEC error.
* However, if this is already in a recovery thread, then
* set up the recovery action thru nfs4_start_recovery and
* return ok back to the outer loop in nfs4_recov_thread for
* recovery.
*/
if (needrecov) {
bool_t abort;
/* If not in a recovery thread, bail out */
if (!isrecov) {
if (!e.error) {
e.error = geterrno4(res.status);
(void) xdr_free(xdr_COMPOUND4res_clnt,
(caddr_t)&res);
}
nfs4args_lookup_free(argop, num_argops);
kmem_free(argop,
lookuparg.arglen * sizeof (nfs_argop4));
kmem_free(tmp_path, path_len + 1);
return (e.error);
}
NFS4_DEBUG(nfs4_client_recov_debug, (CE_NOTE,
"nfs4secinfo_otw: recovery in a recovery thread\n"));
abort = nfs4_start_recovery(&e, mi, NULL,
NULL, NULL, NULL, OP_SECINFO, NULL, NULL, NULL);
if (!e.error) {
e.error = geterrno4(res.status);
(void) xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
}
nfs4args_lookup_free(argop, num_argops);
kmem_free(argop, lookuparg.arglen * sizeof (nfs_argop4));
kmem_free(tmp_path, path_len + 1);
if (abort == FALSE) {
/*
* Return ok to let the outer loop in
* nfs4_recov_thread continue with the recovery action.
*/
return (0);
}
return (e.error);
}
if (res.status) {
nfs4args_lookup_free(argop, num_argops);
kmem_free(argop, lookuparg.arglen * sizeof (nfs_argop4));
(void) xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
kmem_free(tmp_path, path_len + 1);
return (geterrno4(res.status));
}
/*
* Success! Now get the SECINFO result.
*/
numops = res.array_len;
resop = &res.array[numops-1]; /* secinfo res */
ASSERT(resop->resop == OP_SECINFO);
if (resop->nfs_resop4_u.opsecinfo.SECINFO4resok_len == 0) {
/*
* Server does not return any flavor for this export point.
* Return EACCES.
*/
nfs4args_lookup_free(argop, num_argops);
kmem_free(tmp_path, path_len + 1);
(void) xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
kmem_free(argop, num_argops * sizeof (nfs_argop4));
return (EACCES);
}
secinfo_update(mi->mi_curr_serv, &resop->nfs_resop4_u.opsecinfo);
(void) nfs_rw_enter_sig(&svp->sv_lock, RW_READER, 0);
if (svp->sv_secinfo == NULL) {
nfs_rw_exit(&svp->sv_lock);
/*
* This could be because the server requires AUTH_DH, but
* the client does not have netname/syncaddr data
* from sv_dhsec.
*/
nfs4args_lookup_free(argop, num_argops);
kmem_free(argop, lookuparg.arglen * sizeof (nfs_argop4));
(void) xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
kmem_free(tmp_path, path_len + 1);
return (EACCES);
}
nfs_rw_exit(&svp->sv_lock);
/*
* If this is not the original request, try again using the
* new secinfo data in mi.
*/
if (ncomp != tcomp) {
ncomp = tcomp;
nfs4args_lookup_free(argop, num_argops);
kmem_free(argop, lookuparg.arglen * sizeof (nfs_argop4));
(void) xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
kmem_free(tmp_path, path_len + 1);
goto retry;
}
/* Done! */
nfs4args_lookup_free(argop, num_argops);
kmem_free(argop, lookuparg.arglen * sizeof (nfs_argop4));
(void) xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
kmem_free(tmp_path, path_len + 1);
return (0); /* got the secinfo */
}
/*
* Get the security information per mount point.
* Use the server pathname to get the secinfo.
*/
int
nfs4_secinfo_path(mntinfo4_t *mi, cred_t *cr, int isrecov)
{
int error = 0;
int ncomp;
servinfo4_t *svp = mi->mi_curr_serv;
/*
* Get the server pathname that is being mounted on.
*/
(void) nfs_rw_enter_sig(&svp->sv_lock, RW_READER, 0);
ASSERT(svp->sv_path != NULL);
/* returns 0 for root, no matter how many leading /'s */
ncomp = comp_total(svp->sv_path);
/*
* If mounting server rootdir, use available secinfo list
* on the client. No SECINFO call here since SECINFO op
* expects a component name.
*/
if (ncomp == 0) {
if (svp->sv_secinfo == NULL) {
nfs_rw_exit(&svp->sv_lock);
secinfo_update(svp, secinfo_support);
return (0);
}
nfs_rw_exit(&svp->sv_lock);
if (secinfo_check(svp))
return (0); /* try again */
/* no flavors in sv_secinfo work */
return (EACCES);
}
nfs_rw_exit(&svp->sv_lock);
/*
* Get the secinfo from the server.
*/
error = nfs4secinfo_otw(mi, cr, svp, isrecov);
if (error) {
(void) nfs_rw_enter_sig(&svp->sv_lock, RW_WRITER, 0);
if (svp->sv_secinfo) {
if (svp->sv_save_secinfo == svp->sv_secinfo) {
svp->sv_save_secinfo = NULL;
svp->sv_savesec = NULL;
}
secinfo_free(svp->sv_secinfo);
svp->sv_secinfo = NULL;
svp->sv_currsec = NULL;
svp->sv_flags &= ~SV4_TRYSECINFO;
}
if (svp->sv_save_secinfo) {
secinfo_free(svp->sv_save_secinfo);
svp->sv_save_secinfo = NULL;
svp->sv_savesec = NULL;
}
nfs_rw_exit(&svp->sv_lock);
}
return (error);
}
/*
* (secinfo) compound based on a given filehandle and component name.
*
* i.e. (secinfo) PUTFH (fh), SECINFO nm
*/
int
nfs4_secinfo_fh_otw(mntinfo4_t *mi, nfs4_sharedfh_t *fh, char *nm, cred_t *cr)
{
COMPOUND4args_clnt args;
COMPOUND4res_clnt res;
nfs_argop4 argop[2];
nfs_resop4 *resop;
int num_argops, doqueue;
nfs4_error_t e = { 0, NFS4_OK, RPC_SUCCESS };
servinfo4_t *svp;
ASSERT(strlen(nm) > 0);
num_argops = 2; /* Putfh, Secinfo nm */
args.ctag = TAG_SECINFO;
args.array_len = num_argops;
args.array = argop;
/* putfh fh */
argop[0].argop = OP_CPUTFH;
argop[0].nfs_argop4_u.opcputfh.sfh = fh;
/* setup SECINFO op */
argop[1].argop = OP_CSECINFO;
argop[1].nfs_argop4_u.opcsecinfo.cname = nm;
doqueue = 1;
rfs4call(mi, &args, &res, cr, &doqueue, RFSCALL_SOFT, &e);
if (e.error)
return (e.error);
if (res.status) {
(void) xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
return (geterrno4(res.status));
}
/*
* Success! Now get the SECINFO result.
*/
resop = &res.array[1]; /* secinfo res */
ASSERT(resop->resop == OP_SECINFO);
if (resop->nfs_resop4_u.opsecinfo.SECINFO4resok_len == 0) {
/*
* Server does not return any flavor for this export point.
* Return EACCES.
*/
(void) xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
return (EACCES);
}
secinfo_update(mi->mi_curr_serv, &resop->nfs_resop4_u.opsecinfo);
svp = mi->mi_curr_serv;
(void) nfs_rw_enter_sig(&svp->sv_lock, RW_READER, 0);
if (mi->mi_curr_serv->sv_secinfo == NULL) {
nfs_rw_exit(&svp->sv_lock);
/*
* This could be because the server requires AUTH_DH, but
* the client does not have netname/syncaddr data
* from sv_dhsec.
*/
(void) xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
return (EACCES);
}
nfs_rw_exit(&svp->sv_lock);
/* Done! */
(void) xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
return (0); /* got the secinfo */
}
/*
* Making secinfo operation with a given vnode.
*
* This routine is not used by the recovery thread.
* Mainly used in response to NFS4ERR_WRONGSEC from lookup.
*/
int
nfs4_secinfo_vnode_otw(vnode_t *dvp, char *nm, cred_t *cr)
{
ASSERT(strlen(nm) > 0);
return (nfs4_secinfo_fh_otw(VTOMI4(dvp), VTOR4(dvp)->r_fh, nm, cr));
}
/*
* Making secinfo operation with a given vnode if this vnode
* has a parent node. If the given vnode is a root node, use
* the pathname from the mntinfor4_t to do the secinfo call.
*
* This routine is mainly used by the recovery thread.
*/
int
nfs4_secinfo_vnode(vnode_t *vp, cred_t *cr, int isrecov)
{
svnode_t *svp = VTOSV(vp);
char *nm;
int error = 0;
/*
* If there is a parent filehandle, use it to get the secinfo,
* otherwise, use mntinfo4_t pathname to get the secinfo.
*/
if (svp->sv_dfh) {
nm = fn_name(svp->sv_name); /* get the actual component name */
error = nfs4_secinfo_fh_otw(VTOMI4(vp), svp->sv_dfh, nm, cr);
kmem_free(nm, MAXNAMELEN);
} else {
error = nfs4_secinfo_path(VTOMI4(vp), cr, isrecov);
}
return (error);
}
/*
* We are here because the client gets NFS4ERR_WRONGSEC.
*
* Get the security information from the server and indicate
* a set of new security information is here to try.
* Start with the server path that's mounted.
*/
int
nfs4_secinfo_recov(mntinfo4_t *mi, vnode_t *vp1, vnode_t *vp2)
{
int error = 0;
cred_t *cr, *lcr = NULL;
servinfo4_t *svp = mi->mi_curr_serv;
/*
* If the client explicitly specifies a preferred flavor to use
* and gets NFS4ERR_WRONGSEC back, there is no need to negotiate
* the flavor.
*/
(void) nfs_rw_enter_sig(&svp->sv_lock, RW_READER, 0);
if (! (svp->sv_flags & SV4_TRYSECDEFAULT)) {
error = geterrno4(NFS4ERR_WRONGSEC);
nfs_rw_exit(&svp->sv_lock);
} else {
cr = crgetcred();
if (svp->sv_secdata->uid != 0) {
lcr = crdup(cr);
(void) crsetugid(lcr, svp->sv_secdata->uid,
crgetgid(cr));
}
nfs_rw_exit(&svp->sv_lock);
if (vp1 == NULL && vp2 == NULL) {
error = nfs4_secinfo_path(mi, cr, TRUE);
if (lcr && error == EACCES)
error = nfs4_secinfo_path(mi, lcr, TRUE);
} else if (vp1) {
error = nfs4_secinfo_vnode(vp1, cr, TRUE);
if (lcr && error == EACCES)
error = nfs4_secinfo_vnode(vp1, lcr, TRUE);
} /* else */
/* ??? */
crfree(cr);
if (lcr != NULL)
crfree(lcr);
}
mutex_enter(&mi->mi_lock);
mi->mi_recovflags &= ~MI4R_NEED_SECINFO;
mutex_exit(&mi->mi_lock);
return (error);
}