#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kmem.h>
#include <sys/proc.h>
#include <sys/lock.h>
#include <sys/vnode.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/socketvar.h>
#include <sys/cred.h>
#include <netinet/in.h>
#include <inet/ip.h>
#include <inet/ip6.h>
#include <sys/cmn_err.h>
#include <sys/thread.h>
#include <sys/atomic.h>
#include <sys/u8_textprep.h>
#include <netsmb/smb_osdep.h>
#include <netsmb/smb.h>
#include <netsmb/smb2.h>
#include <netsmb/smb_conn.h>
#include <netsmb/smb_subr.h>
#include <netsmb/smb_tran.h>
#include <netsmb/smb_pass.h>
static struct smb_connobj smb_vclist;
void smb_co_init(struct smb_connobj *cp, int level, char *objname);
void smb_co_done(struct smb_connobj *cp);
void smb_co_hold(struct smb_connobj *cp);
void smb_co_rele(struct smb_connobj *cp);
void smb_co_kill(struct smb_connobj *cp);
static void smb_vc_free(struct smb_connobj *cp);
static void smb_vc_gone(struct smb_connobj *cp);
static void smb_share_free(struct smb_connobj *cp);
static void smb_share_gone(struct smb_connobj *cp);
static void smb_fh_free(struct smb_connobj *cp);
static void smb_fh_gone(struct smb_connobj *cp);
int
smb_sm_init(void)
{
smb_co_init(&smb_vclist, SMBL_SM, "smbsm");
return (0);
}
int
smb_sm_idle(void)
{
int error = 0;
SMB_CO_LOCK(&smb_vclist);
if (smb_vclist.co_usecount > 1) {
SMBSDEBUG("%d connections still active\n",
smb_vclist.co_usecount - 1);
error = EBUSY;
}
SMB_CO_UNLOCK(&smb_vclist);
return (error);
}
void
smb_sm_done(void)
{
smb_co_done(&smb_vclist);
}
void
smb_co_init(struct smb_connobj *cp, int level, char *objname)
{
mutex_init(&cp->co_lock, objname, MUTEX_DRIVER, NULL);
cp->co_level = level;
cp->co_usecount = 1;
SLIST_INIT(&cp->co_children);
}
void
smb_co_done(struct smb_connobj *cp)
{
ASSERT(SLIST_EMPTY(&cp->co_children));
mutex_destroy(&cp->co_lock);
}
static void
smb_co_addchild(
struct smb_connobj *parent,
struct smb_connobj *child)
{
ASSERT(child->co_usecount == 1);
child->co_parent = parent;
ASSERT(MUTEX_HELD(&parent->co_lock));
parent->co_usecount++;
SLIST_INSERT_HEAD(&parent->co_children, child, co_next);
}
void
smb_co_hold(struct smb_connobj *cp)
{
SMB_CO_LOCK(cp);
cp->co_usecount++;
SMB_CO_UNLOCK(cp);
}
void
smb_co_rele(struct smb_connobj *co)
{
struct smb_connobj *parent;
int old_flags;
SMB_CO_LOCK(co);
if (co->co_level == SMBL_VC && co->co_usecount == 2) {
smb_vc_t *vcp = CPTOVC(co);
cv_signal(&vcp->iod_idle);
}
if (co->co_usecount > 1) {
co->co_usecount--;
SMB_CO_UNLOCK(co);
return;
}
ASSERT(co->co_usecount == 1);
co->co_usecount = 0;
ASSERT(SLIST_EMPTY(&co->co_children));
old_flags = co->co_flags;
co->co_flags |= SMBO_GONE;
SMB_CO_UNLOCK(co);
if ((old_flags & SMBO_GONE) == 0 && co->co_gone)
co->co_gone(co);
parent = co->co_parent;
if (parent) {
SMB_CO_LOCK(parent);
ASSERT(SLIST_FIRST(&parent->co_children));
if (SLIST_FIRST(&parent->co_children)) {
SLIST_REMOVE(&parent->co_children, co,
smb_connobj, co_next);
}
SMB_CO_UNLOCK(parent);
}
if (co->co_free) {
co->co_free(co);
}
if (parent) {
smb_co_rele(parent);
}
}
void
smb_co_kill(struct smb_connobj *co)
{
int old_flags;
SMB_CO_LOCK(co);
old_flags = co->co_flags;
co->co_flags |= SMBO_GONE;
SMB_CO_UNLOCK(co);
if ((old_flags & SMBO_GONE) == 0 && co->co_gone)
co->co_gone(co);
}
void
smb_vc_hold(struct smb_vc *vcp)
{
smb_co_hold(VCTOCP(vcp));
}
void
smb_vc_rele(struct smb_vc *vcp)
{
smb_co_rele(VCTOCP(vcp));
}
void
smb_vc_kill(struct smb_vc *vcp)
{
smb_co_kill(VCTOCP(vcp));
}
static void
smb_vc_gone(struct smb_connobj *cp)
{
struct smb_vc *vcp = CPTOVC(cp);
smb_iod_disconnect(vcp);
}
static void
smb_vc_free(struct smb_connobj *cp)
{
struct smb_vc *vcp = CPTOVC(cp);
ASSERT(vcp->iod_rqlist.tqh_first == NULL);
if ((vcp->vc_sopt.sv2_capabilities & SMB2_CAP_ENCRYPTION) != 0)
nsmb_crypt_free_mech(vcp);
if (vcp->vc_tdata != NULL)
SMB_TRAN_DONE(vcp);
#ifdef NOTYETDEFINED
if (vcp->vc_tolower)
iconv_close(vcp->vc_tolower);
if (vcp->vc_toupper)
iconv_close(vcp->vc_toupper);
if (vcp->vc_tolocal)
iconv_close(vcp->vc_tolocal);
if (vcp->vc_toserver)
iconv_close(vcp->vc_toserver);
#endif
if (vcp->vc_mackey != NULL)
kmem_free(vcp->vc_mackey, vcp->vc_mackeylen);
if (vcp->vc_ssnkey != NULL)
kmem_free(vcp->vc_ssnkey, vcp->vc_ssnkeylen);
cv_destroy(&vcp->iod_muxwait);
cv_destroy(&vcp->iod_idle);
rw_destroy(&vcp->iod_rqlock);
cv_destroy(&vcp->vc_statechg);
smb_co_done(VCTOCP(vcp));
kmem_free(vcp, sizeof (*vcp));
}
int
smb_vc_create(smbioc_ossn_t *ossn, smb_cred_t *scred, smb_vc_t **vcpp)
{
static char objtype[] = "smb_vc";
cred_t *cr = scred->scr_cred;
struct smb_vc *vcp;
int error = 0;
ASSERT(MUTEX_HELD(&smb_vclist.co_lock));
vcp = kmem_zalloc(sizeof (struct smb_vc), KM_SLEEP);
smb_co_init(VCTOCP(vcp), SMBL_VC, objtype);
vcp->vc_co.co_free = smb_vc_free;
vcp->vc_co.co_gone = smb_vc_gone;
cv_init(&vcp->vc_statechg, objtype, CV_DRIVER, NULL);
rw_init(&vcp->iod_rqlock, objtype, RW_DRIVER, NULL);
cv_init(&vcp->iod_idle, objtype, CV_DRIVER, NULL);
cv_init(&vcp->iod_muxwait, objtype, CV_DRIVER, NULL);
vcp->iod_rqlist.tqh_last = &vcp->iod_rqlist.tqh_first;
vcp->vc_state = SMBIOD_ST_RECONNECT;
vcp->vc_zoneid = getzoneid();
bcopy(ossn, &vcp->vc_ssn, sizeof (*ossn));
vcp->vc_tdesc = &smb_tran_nbtcp_desc;
if ((error = SMB_TRAN_CREATE(vcp, cr)) != 0)
goto errout;
smb_co_addchild(&smb_vclist, VCTOCP(vcp));
*vcpp = vcp;
return (0);
errout:
smb_vc_rele(vcp);
return (error);
}
int
smb_vc_findcreate(smbioc_ossn_t *ossn, smb_cred_t *scred, smb_vc_t **vcpp)
{
struct smb_connobj *co;
struct smb_vc *vcp;
smbioc_ssn_ident_t *vc_id;
int error;
zoneid_t zoneid = getzoneid();
*vcpp = vcp = NULL;
SMB_CO_LOCK(&smb_vclist);
SLIST_FOREACH(co, &smb_vclist.co_children, co_next) {
vcp = CPTOVC(co);
if (vcp->vc_zoneid != zoneid)
continue;
if (vcp->vc_owner != ossn->ssn_owner)
continue;
vc_id = &vcp->vc_ssn.ssn_id;
if (bcmp(&vc_id->id_srvaddr,
&ossn->ssn_id.id_srvaddr,
sizeof (vc_id->id_srvaddr)))
continue;
if (u8_strcmp(vc_id->id_user, ossn->ssn_id.id_user, 0,
U8_STRCMP_CI_LOWER, U8_UNICODE_LATEST, &error))
continue;
if (u8_strcmp(vc_id->id_domain, ossn->ssn_id.id_domain, 0,
U8_STRCMP_CI_LOWER, U8_UNICODE_LATEST, &error))
continue;
SMB_VC_LOCK(vcp);
if ((vcp->vc_flags & SMBV_GONE) == 0) {
ossn->ssn_vopt &= ~SMBVOPT_CREATE;
co->co_usecount++;
SMB_VC_UNLOCK(vcp);
*vcpp = vcp;
error = 0;
goto out;
}
SMB_VC_UNLOCK(vcp);
}
vcp = NULL;
if (ossn->ssn_vopt & SMBVOPT_CREATE) {
error = smb_vc_create(ossn, scred, &vcp);
if (error == 0)
*vcpp = vcp;
} else
error = ENOENT;
out:
SMB_CO_UNLOCK(&smb_vclist);
return (error);
}
void *
smb_vc_getipaddr(struct smb_vc *vcp, int *ipvers)
{
smbioc_ssn_ident_t *id = &vcp->vc_ssn.ssn_id;
void *ret;
switch (id->id_srvaddr.sa.sa_family) {
case AF_INET:
*ipvers = IPV4_VERSION;
ret = &id->id_srvaddr.sin.sin_addr;
break;
case AF_INET6:
*ipvers = IPV6_VERSION;
ret = &id->id_srvaddr.sin6.sin6_addr;
break;
default:
SMBSDEBUG("invalid address family %d\n",
id->id_srvaddr.sa.sa_family);
*ipvers = 0;
ret = NULL;
break;
}
return (ret);
}
void
smb_vc_walkshares(struct smb_vc *vcp,
walk_share_func_t func)
{
smb_connobj_t *co;
smb_share_t *ssp;
SMB_VC_LOCK(vcp);
SLIST_FOREACH(co, &(VCTOCP(vcp)->co_children), co_next) {
ssp = CPTOSS(co);
SMB_SS_LOCK(ssp);
func(ssp);
SMB_SS_UNLOCK(ssp);
}
SMB_VC_UNLOCK(vcp);
}
void
smb_share_hold(struct smb_share *ssp)
{
smb_co_hold(SSTOCP(ssp));
}
void
smb_share_rele(struct smb_share *ssp)
{
smb_co_rele(SSTOCP(ssp));
}
void
smb_share_kill(struct smb_share *ssp)
{
smb_co_kill(SSTOCP(ssp));
}
static void
smb_share_gone(struct smb_connobj *cp)
{
struct smb_cred scred;
struct smb_share *ssp = CPTOSS(cp);
smb_vc_t *vcp = SSTOVC(ssp);
smb_credinit(&scred, NULL);
smb_iod_shutdown_share(ssp);
if (vcp->vc_flags & SMBV_SMB2)
(void) smb2_smb_treedisconnect(ssp, &scred);
else
(void) smb_smb_treedisconnect(ssp, &scred);
smb_credrele(&scred);
}
static void
smb_share_free(struct smb_connobj *cp)
{
struct smb_share *ssp = CPTOSS(cp);
cv_destroy(&ssp->ss_conn_done);
smb_co_done(SSTOCP(ssp));
kmem_free(ssp, sizeof (*ssp));
}
int
smb_share_create(smbioc_tcon_t *tcon, struct smb_vc *vcp,
struct smb_share **sspp, struct smb_cred *scred)
{
static char objtype[] = "smb_ss";
struct smb_share *ssp;
ASSERT(MUTEX_HELD(&vcp->vc_lock));
ssp = kmem_zalloc(sizeof (struct smb_share), KM_SLEEP);
smb_co_init(SSTOCP(ssp), SMBL_SHARE, objtype);
ssp->ss_co.co_free = smb_share_free;
ssp->ss_co.co_gone = smb_share_gone;
cv_init(&ssp->ss_conn_done, objtype, CV_DRIVER, NULL);
ssp->ss_tid = SMB_TID_UNKNOWN;
ssp->ss2_tree_id = SMB2_TID_UNKNOWN;
bcopy(&tcon->tc_sh, &ssp->ss_ioc,
sizeof (smbioc_oshare_t));
smb_co_addchild(VCTOCP(vcp), SSTOCP(ssp));
*sspp = ssp;
return (0);
}
int
smb_share_findcreate(smbioc_tcon_t *tcon, struct smb_vc *vcp,
struct smb_share **sspp, struct smb_cred *scred)
{
struct smb_connobj *co;
struct smb_share *ssp = NULL;
int error = 0;
*sspp = NULL;
SMB_VC_LOCK(vcp);
SLIST_FOREACH(co, &(VCTOCP(vcp)->co_children), co_next) {
ssp = CPTOSS(co);
if (u8_strcmp(ssp->ss_name, tcon->tc_sh.sh_name, 0,
U8_STRCMP_CI_LOWER, U8_UNICODE_LATEST, &error))
continue;
SMB_SS_LOCK(ssp);
if ((ssp->ss_flags & SMBS_GONE) == 0) {
tcon->tc_opt &= ~SMBSOPT_CREATE;
co->co_usecount++;
SMB_SS_UNLOCK(ssp);
*sspp = ssp;
error = 0;
goto out;
}
SMB_SS_UNLOCK(ssp);
}
ssp = NULL;
if (tcon->tc_opt & SMBSOPT_CREATE) {
error = smb_share_create(tcon, vcp, &ssp, scred);
if (error == 0)
*sspp = ssp;
} else
error = ENOENT;
out:
SMB_VC_UNLOCK(vcp);
return (error);
}
void
smb_share_invalidate(struct smb_share *ssp)
{
ASSERT(MUTEX_HELD(&ssp->ss_lock));
ssp->ss_flags &= ~SMBS_CONNECTED;
ssp->ss_tid = SMB_TID_UNKNOWN;
ssp->ss_vcgenid = 0;
}
int
smb_share_tcon(smb_share_t *ssp, smb_cred_t *scred)
{
smb_vc_t *vcp = SSTOVC(ssp);
clock_t tmo;
int error;
SMB_SS_LOCK(ssp);
if (ssp->ss_flags & SMBS_CONNECTED) {
SMBIODEBUG("alread connected?");
error = 0;
goto out;
}
while (ssp->ss_flags & SMBS_RECONNECTING) {
ssp->ss_conn_waiters++;
tmo = cv_wait_sig(&ssp->ss_conn_done, &ssp->ss_lock);
ssp->ss_conn_waiters--;
if (tmo == 0) {
error = EINTR;
goto out;
}
}
if (ssp->ss_flags & SMBS_CONNECTED) {
error = 0;
goto out;
}
ssp->ss_flags |= SMBS_RECONNECTING;
SMB_SS_UNLOCK(ssp);
if (vcp->vc_flags & SMBV_SMB2)
error = smb2_smb_treeconnect(ssp, scred);
else
error = smb_smb_treeconnect(ssp, scred);
SMB_SS_LOCK(ssp);
ssp->ss_flags &= ~SMBS_RECONNECTING;
if (ssp->ss_conn_waiters)
cv_broadcast(&ssp->ss_conn_done);
out:
SMB_SS_UNLOCK(ssp);
return (error);
}
void
smb_fh_hold(struct smb_fh *fhp)
{
smb_co_hold(FHTOCP(fhp));
}
void
smb_fh_rele(struct smb_fh *fhp)
{
smb_co_rele(FHTOCP(fhp));
}
void
smb_fh_close(struct smb_fh *fhp)
{
smb_co_kill(FHTOCP(fhp));
}
static void
smb_fh_gone(struct smb_connobj *cp)
{
struct smb_cred scred;
struct smb_fh *fhp = CPTOFH(cp);
smb_share_t *ssp = FHTOSS(fhp);
int err;
if ((fhp->fh_flags & SMBFH_VALID) == 0)
return;
if (fhp->fh_vcgenid != ssp->ss_vcgenid)
return;
smb_credinit(&scred, NULL);
err = smb_smb_close(ssp, fhp, &scred);
smb_credrele(&scred);
if (err) {
SMBSDEBUG("close err=%d\n", err);
}
}
static void
smb_fh_free(struct smb_connobj *cp)
{
struct smb_fh *fhp = CPTOFH(cp);
smb_co_done(FHTOCP(fhp));
kmem_free(fhp, sizeof (*fhp));
}
int
smb_fh_create(smb_share_t *ssp, struct smb_fh **fhpp)
{
static char objtype[] = "smb_fh";
struct smb_fh *fhp;
fhp = kmem_zalloc(sizeof (struct smb_fh), KM_SLEEP);
smb_co_init(FHTOCP(fhp), SMBL_FH, objtype);
fhp->fh_co.co_free = smb_fh_free;
fhp->fh_co.co_gone = smb_fh_gone;
SMB_SS_LOCK(ssp);
if ((ssp->ss_flags & SMBS_GONE) != 0) {
SMB_SS_UNLOCK(ssp);
smb_fh_free(FHTOCP(fhp));
return (ENOTCONN);
}
smb_co_addchild(SSTOCP(ssp), FHTOCP(fhp));
*fhpp = fhp;
SMB_SS_UNLOCK(ssp);
return (0);
}
void
smb_fh_opened(struct smb_fh *fhp)
{
smb_share_t *ssp = FHTOSS(fhp);
SMB_FH_LOCK(fhp);
fhp->fh_vcgenid = ssp->ss_vcgenid;
fhp->fh_flags |= SMBFH_VALID;
SMB_FH_UNLOCK(fhp);
}
void
lingering_vc(struct smb_vc *vc)
{
DEBUG_ENTER("lingering VC");
}
void
nsmb_zone_shutdown(zoneid_t zoneid, void *data)
{
struct smb_connobj *co;
struct smb_vc *vcp;
SMB_CO_LOCK(&smb_vclist);
SLIST_FOREACH(co, &smb_vclist.co_children, co_next) {
vcp = CPTOVC(co);
if (vcp->vc_zoneid != zoneid)
continue;
smb_vc_kill(vcp);
}
SMB_CO_UNLOCK(&smb_vclist);
}
void
nsmb_zone_destroy(zoneid_t zoneid, void *data)
{
struct smb_connobj *co;
struct smb_vc *vcp;
SMB_CO_LOCK(&smb_vclist);
if (smb_vclist.co_usecount > 1) {
SMBERROR("%d connections still active\n",
smb_vclist.co_usecount - 1);
}
SLIST_FOREACH(co, &smb_vclist.co_children, co_next) {
vcp = CPTOVC(co);
if (vcp->vc_zoneid != zoneid)
continue;
lingering_vc(vcp);
}
SMB_CO_UNLOCK(&smb_vclist);
}