#include <sys/param.h>
#include <sys/kmem.h>
#include <sys/systm.h>
#include <sys/policy.h>
#include <sys/conf.h>
#include <sys/proc.h>
#include <sys/fcntl.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <sys/sunddi.h>
#include <sys/cmn_err.h>
#include <netsmb/smb_osdep.h>
#include <smb/winioctl.h>
#include <netsmb/smb.h>
#include <netsmb/smb_conn.h>
#include <netsmb/smb_rq.h>
#include <netsmb/smb_subr.h>
#include <netsmb/smb_dev.h>
static int smb_cpdatain(struct mbchain *mbp, int len, char *data, int seg);
int
smb_usr_get_ssnkey(smb_dev_t *sdp, intptr_t arg, int flags)
{
struct smb_vc *vcp = NULL;
if ((vcp = sdp->sd_vc) == NULL)
return (ENOTCONN);
if (vcp->vc_state != SMBIOD_ST_VCACTIVE)
return (ENOTCONN);
if (vcp->vc_ssnkey == NULL ||
vcp->vc_ssnkeylen < SMBIOC_HASH_SZ)
return (EINVAL);
if (ddi_copyout(vcp->vc_ssnkey, (void *)arg,
SMBIOC_HASH_SZ, flags))
return (EFAULT);
return (0);
}
int
smb_usr_xnp(smb_dev_t *sdp, intptr_t arg, int flags, cred_t *cr)
{
struct smb_cred scred;
struct smb_share *ssp;
struct smb_fh *fhp;
smbioc_xnp_t *ioc = NULL;
struct mbchain send_mb;
struct mdchain recv_md;
uint32_t rdlen;
int err, mbseg;
if ((fhp = sdp->sd_fh) == NULL)
return (EINVAL);
ssp = FHTOSS(fhp);
if (fhp->fh_vcgenid != ssp->ss_vcgenid)
return (ESTALE);
bzero(&send_mb, sizeof (send_mb));
bzero(&recv_md, sizeof (recv_md));
ioc = kmem_alloc(sizeof (*ioc), KM_SLEEP);
if (ddi_copyin((void *) arg, ioc, sizeof (*ioc), flags)) {
err = EFAULT;
goto out;
}
mbseg = (flags & FKIOCTL) ? MB_MSYSTEM : MB_MUSER;
err = smb_cpdatain(&send_mb, ioc->ioc_tdlen, ioc->ioc_tdata, mbseg);
if (err)
goto out;
rdlen = ioc->ioc_rdlen;
smb_credinit(&scred, cr);
if (SSTOVC(ssp)->vc_flags & SMBV_SMB2) {
err = smb2_smb_ioctl(ssp, &fhp->fh_fid2,
&send_mb, &recv_md, &rdlen,
FSCTL_PIPE_TRANSCEIVE, &scred);
} else {
err = smb_t2_xnp(ssp, fhp->fh_fid1,
&send_mb, &recv_md, &rdlen,
&ioc->ioc_more, &scred);
}
smb_credrele(&scred);
if (err == 0 && recv_md.md_top != NULL) {
size_t len = m_fixhdr(recv_md.md_top);
if (len > ioc->ioc_rdlen) {
err = EMSGSIZE;
goto out;
}
err = md_get_mem(&recv_md, ioc->ioc_rdata, len, mbseg);
if (err)
goto out;
} else
ioc->ioc_rdlen = 0;
if (rdlen <= ioc->ioc_rdlen) {
ioc->ioc_rdlen = rdlen;
} else {
ioc->ioc_more = 1;
}
(void) ddi_copyout(ioc, (void *)arg, sizeof (*ioc), flags);
out:
kmem_free(ioc, sizeof (*ioc));
md_done(&recv_md);
mb_done(&send_mb);
return (err);
}
static int
smb_cpdatain(struct mbchain *mbp, int len, char *data, int mbseg)
{
int error;
if (len == 0)
return (0);
error = mb_init(mbp);
if (error)
return (error);
return (mb_put_mem(mbp, data, len, mbseg));
}
int
smb_usr_rw(smb_dev_t *sdp, int cmd, intptr_t arg, int flags, cred_t *cr)
{
struct smb_cred scred;
struct smb_share *ssp;
struct smb_fh *fhp;
smbioc_rw_t *ioc = NULL;
struct iovec aiov[1];
struct uio auio;
int err;
uio_rw_t rw;
if ((fhp = sdp->sd_fh) == NULL)
return (EINVAL);
ssp = FHTOSS(fhp);
if (fhp->fh_vcgenid != ssp->ss_vcgenid)
return (ESTALE);
ioc = kmem_alloc(sizeof (*ioc), KM_SLEEP);
if (ddi_copyin((void *) arg, ioc, sizeof (*ioc), flags)) {
err = EFAULT;
goto out;
}
switch (cmd) {
case SMBIOC_READ:
rw = UIO_READ;
break;
case SMBIOC_WRITE:
rw = UIO_WRITE;
break;
default:
err = ENODEV;
goto out;
}
aiov[0].iov_base = ioc->ioc_base;
aiov[0].iov_len = (size_t)ioc->ioc_cnt;
auio.uio_iov = aiov;
auio.uio_iovcnt = 1;
auio.uio_loffset = ioc->ioc_offset;
auio.uio_segflg = (flags & FKIOCTL) ?
UIO_SYSSPACE : UIO_USERSPACE;
auio.uio_fmode = 0;
auio.uio_resid = (size_t)ioc->ioc_cnt;
smb_credinit(&scred, cr);
err = smb_rwuio(fhp, rw, &auio, &scred, 0);
smb_credrele(&scred);
ioc->ioc_cnt -= auio.uio_resid;
(void) ddi_copyout(ioc, (void *)arg, sizeof (*ioc), flags);
out:
kmem_free(ioc, sizeof (*ioc));
return (err);
}
int
smb_usr_ntcreate(smb_dev_t *sdp, intptr_t arg, int flags, cred_t *cr)
{
struct smb_cred scred;
struct mbchain name_mb;
struct smb_share *ssp;
struct smb_fh *fhp = NULL;
smbioc_ntcreate_t *ioc = NULL;
int err, nmlen;
mb_init(&name_mb);
if ((ssp = sdp->sd_share) == NULL)
return (ENOTCONN);
if (sdp->sd_fh != NULL)
return (EINVAL);
ioc = kmem_alloc(sizeof (*ioc), KM_SLEEP);
if (ddi_copyin((void *) arg, ioc, sizeof (*ioc), flags)) {
err = EFAULT;
goto out;
}
ioc->ioc_name[SMBIOC_MAX_NAME-1] = '\0';
nmlen = strnlen(ioc->ioc_name, SMBIOC_MAX_NAME-1);
err = smb_put_dmem(&name_mb, SSTOVC(ssp),
ioc->ioc_name, nmlen,
SMB_CS_NONE, NULL);
if (err != 0)
goto out;
err = smb_fh_create(ssp, &fhp);
if (err != 0)
goto out;
smb_credinit(&scred, cr);
err = smb_smb_ntcreate(ssp, &name_mb,
0,
ioc->ioc_req_acc,
ioc->ioc_efattr,
ioc->ioc_share_acc,
ioc->ioc_open_disp,
ioc->ioc_creat_opts,
NTCREATEX_IMPERSONATION_IMPERSONATION,
&scred,
fhp,
NULL,
NULL);
smb_credrele(&scred);
if (err != 0)
goto out;
fhp->fh_rights = ioc->ioc_req_acc;
smb_fh_opened(fhp);
sdp->sd_fh = fhp;
fhp = NULL;
out:
if (fhp != NULL)
smb_fh_rele(fhp);
kmem_free(ioc, sizeof (*ioc));
mb_done(&name_mb);
return (err);
}
int
smb_usr_printjob(smb_dev_t *sdp, intptr_t arg, int flags, cred_t *cr)
{
static const char invalid_chars[] = SMB_FILENAME_INVALID_CHARS;
struct smb_cred scred;
struct mbchain name_mb;
struct smb_share *ssp;
struct smb_fh *fhp = NULL;
smbioc_printjob_t *ioc = NULL;
int err, cklen, nmlen;
uint32_t access = SA_RIGHT_FILE_WRITE_DATA |
SA_RIGHT_FILE_READ_ATTRIBUTES;
mb_init(&name_mb);
if ((ssp = sdp->sd_share) == NULL)
return (ENOTCONN);
if (ssp->ss_type != STYPE_PRINTQ)
return (EINVAL);
if (sdp->sd_fh != NULL)
return (EINVAL);
smb_credinit(&scred, cr);
ioc = kmem_alloc(sizeof (*ioc), KM_SLEEP);
if (ddi_copyin((void *) arg, ioc, sizeof (*ioc), flags)) {
err = EFAULT;
goto out;
}
ioc->ioc_title[SMBIOC_MAX_NAME-1] = '\0';
nmlen = strnlen(ioc->ioc_title, SMBIOC_MAX_NAME-1);
cklen = strcspn(ioc->ioc_title, invalid_chars);
if (cklen < nmlen) {
err = EINVAL;
goto out;
}
err = smb_put_dmem(&name_mb, SSTOVC(ssp),
ioc->ioc_title, nmlen,
SMB_CS_NONE, NULL);
if (err != 0)
goto out;
err = smb_fh_create(ssp, &fhp);
if (err != 0)
goto out;
smb_credinit(&scred, cr);
if (SSTOVC(ssp)->vc_flags & SMBV_SMB2) {
err = smb2_smb_ntcreate(ssp, &name_mb,
NULL, NULL,
0,
access,
SMB_EFA_NORMAL,
NTCREATEX_SHARE_ACCESS_NONE,
NTCREATEX_DISP_CREATE,
NTCREATEX_OPTIONS_NON_DIRECTORY_FILE,
NTCREATEX_IMPERSONATION_IMPERSONATION,
&scred,
&fhp->fh_fid2,
NULL,
NULL);
} else {
err = smb_smb_open_prjob(ssp, ioc->ioc_title,
ioc->ioc_setuplen, ioc->ioc_prmode,
&scred, &fhp->fh_fid1);
}
smb_credrele(&scred);
if (err != 0)
goto out;
fhp->fh_rights = access;
smb_fh_opened(fhp);
sdp->sd_fh = fhp;
fhp = NULL;
out:
if (fhp != NULL)
smb_fh_rele(fhp);
kmem_free(ioc, sizeof (*ioc));
mb_done(&name_mb);
return (err);
}
int
smb_usr_closefh(smb_dev_t *sdp, cred_t *cr)
{
struct smb_fh *fhp;
if ((fhp = sdp->sd_fh) == NULL)
return (EINVAL);
sdp->sd_fh = NULL;
smb_fh_close(fhp);
smb_fh_rele(fhp);
return (0);
}
int
smb_usr_get_ssn(smb_dev_t *sdp, int cmd, intptr_t arg, int flags, cred_t *cr)
{
struct smb_cred scred;
smbioc_ossn_t *ossn = NULL;
struct smb_vc *vcp = NULL;
int error = 0;
uid_t realuid;
if (sdp->sd_vc != NULL)
return (EISCONN);
smb_credinit(&scred, cr);
ossn = kmem_alloc(sizeof (*ossn), KM_SLEEP);
if (ddi_copyin((void *)arg, ossn, sizeof (*ossn), flags)) {
error = EFAULT;
goto out;
}
realuid = crgetruid(cr);
if (ossn->ssn_owner == SMBM_ANY_OWNER)
ossn->ssn_owner = realuid;
else {
if (secpolicy_vnode_owner(cr, ossn->ssn_owner)) {
error = EPERM;
goto out;
}
}
ossn->ssn_srvname[SMBIOC_MAX_NAME-1] = '\0';
ossn->ssn_id.id_domain[ SMBIOC_MAX_NAME-1] = '\0';
ossn->ssn_id.id_user[ SMBIOC_MAX_NAME-1] = '\0';
if (cmd == SMBIOC_SSN_CREATE)
ossn->ssn_vopt |= SMBVOPT_CREATE;
else
ossn->ssn_vopt &= ~SMBVOPT_CREATE;
error = smb_vc_findcreate(ossn, &scred, &vcp);
if (error)
goto out;
ASSERT(vcp != NULL);
if (cmd == SMBIOC_SSN_CREATE) {
if (vcp->iod_thr != NULL) {
error = EEXIST;
goto out;
}
sdp->sd_flags |= NSMBFL_IOD;
} else {
if (vcp->vc_state != SMBIOD_ST_VCACTIVE) {
error = smb_iod_reconnect(vcp);
if (error != 0)
goto out;
}
}
sdp->sd_level = SMBL_VC;
sdp->sd_vc = vcp;
vcp = NULL;
(void) ddi_copyout(ossn, (void *)arg, sizeof (*ossn), flags);
out:
if (vcp) {
smb_vc_rele(vcp);
}
kmem_free(ossn, sizeof (*ossn));
smb_credrele(&scred);
return (error);
}
int
smb_usr_drop_ssn(smb_dev_t *sdp, int cmd)
{
struct smb_vc *vcp = NULL;
if ((vcp = sdp->sd_vc) == NULL)
return (ENOTCONN);
if (sdp->sd_share) {
smb_share_rele(sdp->sd_share);
sdp->sd_share = NULL;
sdp->sd_level = SMBL_VC;
}
if (cmd == SMBIOC_SSN_KILL)
smb_vc_kill(vcp);
smb_vc_rele(vcp);
sdp->sd_vc = NULL;
sdp->sd_level = 0;
return (0);
}
int
smb_usr_get_tree(smb_dev_t *sdp, int cmd, intptr_t arg, int flags, cred_t *cr)
{
struct smb_cred scred;
smbioc_tcon_t *tcon = NULL;
struct smb_vc *vcp = NULL;
struct smb_share *ssp = NULL;
int error = 0;
if ((vcp = sdp->sd_vc) == NULL)
return (ENOTCONN);
if (sdp->sd_share != NULL)
return (EISCONN);
smb_credinit(&scred, cr);
tcon = kmem_alloc(sizeof (*tcon), KM_SLEEP);
if (ddi_copyin((void *)arg, tcon, sizeof (*tcon), flags)) {
error = EFAULT;
goto out;
}
tcon->tc_sh.sh_name[SMBIOC_MAX_NAME-1] = '\0';
tcon->tc_sh.sh_pass[SMBIOC_MAX_NAME-1] = '\0';
if (cmd == SMBIOC_TREE_CONNECT)
tcon->tc_opt |= SMBSOPT_CREATE;
else
tcon->tc_opt &= ~SMBSOPT_CREATE;
error = smb_share_findcreate(tcon, vcp, &ssp, &scred);
if (error)
goto out;
ASSERT(ssp != NULL);
if (cmd == SMBIOC_TREE_CONNECT) {
error = smb_share_tcon(ssp, &scred);
if (error)
goto out;
}
tcon->tc_sh.sh_type = ssp->ss_type;
sdp->sd_level = SMBL_SHARE;
sdp->sd_share = ssp;
ssp = NULL;
(void) ddi_copyout(tcon, (void *)arg, sizeof (*tcon), flags);
out:
if (ssp) {
smb_share_rele(ssp);
}
bzero(tcon, sizeof (*tcon));
kmem_free(tcon, sizeof (*tcon));
smb_credrele(&scred);
return (error);
}
int
smb_usr_drop_tree(smb_dev_t *sdp, int cmd)
{
struct smb_share *ssp = NULL;
if (sdp->sd_vc == NULL)
return (ENOTCONN);
if ((ssp = sdp->sd_share) == NULL)
return (ENOTCONN);
if (cmd == SMBIOC_TREE_KILL)
smb_share_kill(ssp);
smb_share_rele(sdp->sd_share);
sdp->sd_share = NULL;
sdp->sd_level = SMBL_VC;
return (0);
}
int
smb_usr_iod_ioctl(smb_dev_t *sdp, int cmd, intptr_t arg, int flags, cred_t *cr)
{
struct smb_vc *vcp;
int err = 0;
if ((sdp->sd_flags & NSMBFL_IOD) == 0)
return (EINVAL);
if ((vcp = sdp->sd_vc) == NULL)
return (EINVAL);
if (sdp->sd_share != NULL)
return (EINVAL);
SMB_VC_LOCK(vcp);
if (vcp->iod_thr == NULL)
vcp->iod_thr = curthread;
else
err = EEXIST;
SMB_VC_UNLOCK(vcp);
if (err)
return (err);
if (ddi_copyin((void *)arg, &vcp->vc_work,
sizeof (smbioc_ssn_work_t), flags)) {
err = EFAULT;
goto out;
}
vcp->vc_work.wk_out_state = 0;
switch (cmd) {
case SMBIOC_IOD_CONNECT:
err = nsmb_iod_connect(vcp, cr);
break;
case SMBIOC_IOD_NEGOTIATE:
err = nsmb_iod_negotiate(vcp, cr);
break;
case SMBIOC_IOD_SSNSETUP:
err = nsmb_iod_ssnsetup(vcp, cr);
break;
case SMBIOC_IOD_WORK:
err = smb_iod_vc_work(vcp, flags, cr);
break;
case SMBIOC_IOD_IDLE:
err = smb_iod_vc_idle(vcp);
break;
case SMBIOC_IOD_RCFAIL:
err = smb_iod_vc_rcfail(vcp);
break;
default:
err = ENOTTY;
break;
}
out:
vcp->vc_work.wk_out_state = vcp->vc_state;
(void) ddi_copyout(&vcp->vc_work, (void *)arg,
sizeof (smbioc_ssn_work_t), flags);
SMB_VC_LOCK(vcp);
vcp->iod_thr = NULL;
cv_broadcast(&vcp->vc_statechg);
SMB_VC_UNLOCK(vcp);
return (err);
}
int
smb_usr_ioctl(smb_dev_t *sdp, int cmd, intptr_t arg, int flags, cred_t *cr)
{
int err;
mutex_enter(&sdp->sd_lock);
if ((sdp->sd_flags & NSMBFL_IOCTL) != 0) {
mutex_exit(&sdp->sd_lock);
return (EBUSY);
}
sdp->sd_flags |= NSMBFL_IOCTL;
mutex_exit(&sdp->sd_lock);
err = 0;
switch (cmd) {
case SMBIOC_GETVERS:
(void) ddi_copyout(&nsmb_version, (void *)arg,
sizeof (nsmb_version), flags);
break;
case SMBIOC_GETSSNKEY:
err = smb_usr_get_ssnkey(sdp, arg, flags);
break;
case SMBIOC_DUP_DEV:
err = smb_usr_dup_dev(sdp, arg, flags);
break;
case SMBIOC_XACTNP:
err = smb_usr_xnp(sdp, arg, flags, cr);
break;
case SMBIOC_READ:
case SMBIOC_WRITE:
err = smb_usr_rw(sdp, cmd, arg, flags, cr);
break;
case SMBIOC_NTCREATE:
err = smb_usr_ntcreate(sdp, arg, flags, cr);
break;
case SMBIOC_PRINTJOB:
err = smb_usr_printjob(sdp, arg, flags, cr);
break;
case SMBIOC_CLOSEFH:
err = smb_usr_closefh(sdp, cr);
break;
case SMBIOC_SSN_CREATE:
case SMBIOC_SSN_FIND:
err = smb_usr_get_ssn(sdp, cmd, arg, flags, cr);
break;
case SMBIOC_SSN_KILL:
case SMBIOC_SSN_RELE:
err = smb_usr_drop_ssn(sdp, cmd);
break;
case SMBIOC_TREE_CONNECT:
case SMBIOC_TREE_FIND:
err = smb_usr_get_tree(sdp, cmd, arg, flags, cr);
break;
case SMBIOC_TREE_KILL:
case SMBIOC_TREE_RELE:
err = smb_usr_drop_tree(sdp, cmd);
break;
case SMBIOC_IOD_CONNECT:
case SMBIOC_IOD_NEGOTIATE:
case SMBIOC_IOD_SSNSETUP:
case SMBIOC_IOD_WORK:
case SMBIOC_IOD_IDLE:
case SMBIOC_IOD_RCFAIL:
err = smb_usr_iod_ioctl(sdp, cmd, arg, flags, cr);
break;
case SMBIOC_PK_ADD:
case SMBIOC_PK_DEL:
case SMBIOC_PK_CHK:
case SMBIOC_PK_DEL_OWNER:
case SMBIOC_PK_DEL_EVERYONE:
err = smb_pkey_ioctl(cmd, arg, flags, cr);
break;
default:
err = ENOTTY;
break;
}
mutex_enter(&sdp->sd_lock);
sdp->sd_flags &= ~NSMBFL_IOCTL;
mutex_exit(&sdp->sd_lock);
return (err);
}