root/usr/src/uts/common/fs/nfs/nfs4_client_secinfo.c
/*
 * 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) {
                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)
                        xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
                return (0);
        }

        if (res.status) {
                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.
         */
        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 = 0;
        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));
                        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));
                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);
                                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);
                        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));
                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);
                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));
                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));
                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));
        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) {
                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.
                 */
                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.
                 */
                xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
                return (EACCES);
        }
        nfs_rw_exit(&svp->sv_lock);

        /* Done! */
        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);
}