#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/ioccom.h>
#include <sys/malloc.h>
#include <sys/uio.h>
#include <sys/conf.h>
#include <sys/kpi_mbuf.h>
#include <sys/proc.h>
#include <sys/fcntl.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/sysctl.h>
#include <sys/vnode.h>
#include <sys/kauth.h>
#include <net/if.h>
#include <sys/smb_apple.h>
#include <sys/mchain.h>
#include <netsmb/smb.h>
#include <netsmb/smb_conn.h>
#include <netsmb/smb_subr.h>
#include <netsmb/smb_dev.h>
#include <netsmb/smb_tran.h>
#define SMBMINORS 1024
struct smb_dev * smb_dtab[SMBMINORS];
int smb_minor_hiwat = -1;
#define SMB_GETDEV(dev) (smb_dtab[minor(dev)])
#define SMB_CHECKMINOR(sdp, dev) do { \
sdp = SMB_GETDEV(dev); \
if (sdp == NULL) \
return (ENXIO); \
} while(0)
static d_open_t nsmb_dev_open;
static d_close_t nsmb_dev_close;
static d_ioctl_t nsmb_dev_ioctl;
static struct cdevsw nsmb_cdevsw = {
nsmb_dev_open,
nsmb_dev_close,
eno_rdwrt,
eno_rdwrt,
nsmb_dev_ioctl,
eno_stop,
eno_reset,
0,
eno_select,
eno_mmap,
eno_strat,
eno_getc,
eno_putc,
0
};
int smb_major = -1;
extern lck_mtx_t * dev_lck;
static int
nsmb_dev_open_nolock(dev_t dev, int oflags, int devtype, struct proc *p)
{
#pragma unused(oflags, devtype, p)
struct smb_dev *sdp;
kauth_cred_t cred = vfs_context_ucred(vfs_context_current());
sdp = SMB_GETDEV(dev);
if (sdp && (sdp->sd_flags & NSMBFL_OPEN))
return (EBUSY);
if (!sdp || minor(dev) == 0) {
int avail_minor;
for (avail_minor = 1; avail_minor < SMBMINORS; avail_minor++)
if (!SMB_GETDEV(avail_minor))
break;
if (avail_minor >= SMBMINORS)
panic("smb: %d minor devices!", avail_minor);
sdp = malloc(sizeof(*sdp), M_NSMBDEV, M_WAITOK);
bzero(sdp, sizeof(*sdp));
dev = makedev(smb_major, avail_minor);
sdp->sd_devfs = devfs_make_node(dev, DEVFS_CHAR,
kauth_cred_getuid(cred),
kauth_cred_getgid(cred),
0700, "nsmb%x", avail_minor);
if (!sdp->sd_devfs)
panic("smb: devfs_make_node 0700");
if (avail_minor > smb_minor_hiwat)
smb_minor_hiwat = avail_minor;
SMB_GETDEV(dev) = sdp;
return (EBUSY);
}
sdp->sd_flags |= NSMBFL_OPEN;
return (0);
}
static int
nsmb_dev_open(dev_t dev, int oflags, int devtype, struct proc *p)
{
int error;
DBG_ASSERT(sizeof(struct smbioc_negotiate) < SMB_MAX_IOC_SIZE);
DBG_ASSERT(sizeof(struct smbioc_setup) < SMB_MAX_IOC_SIZE);
DBG_ASSERT(sizeof(struct smbioc_share) < SMB_MAX_IOC_SIZE);
DBG_ASSERT(sizeof(struct smbioc_rq) < SMB_MAX_IOC_SIZE);
DBG_ASSERT(sizeof(struct smbioc_t2rq) < SMB_MAX_IOC_SIZE);
DBG_ASSERT(sizeof(struct smbioc_rw) < SMB_MAX_IOC_SIZE);
lck_mtx_lock(dev_lck);
error = nsmb_dev_open_nolock(dev, oflags, devtype, p);
lck_mtx_unlock(dev_lck);
return (error);
}
static int
nsmb_dev_close_nolock(dev_t dev, int flag, int fmt, struct proc *p)
{
#pragma unused(flag, fmt, p)
struct smb_dev *sdp;
struct smb_vc *vcp;
struct smb_share *ssp;
vfs_context_t context;
SMB_CHECKMINOR(sdp, dev);
if ((sdp->sd_flags & NSMBFL_OPEN) == 0)
return (EBADF);
context = vfs_context_create((vfs_context_t)0);
ssp = sdp->sd_share;
sdp->sd_share = NULL;
if (ssp != NULL)
smb_share_rele(ssp, context);
vcp = sdp->sd_vc;
sdp->sd_vc = NULL;
if (vcp != NULL)
smb_vc_rele(vcp, context);
devfs_remove(sdp->sd_devfs);
vfs_context_rele(context);
SMB_GETDEV(dev) = NULL;
free(sdp, M_NSMBDEV);
return (0);
}
static int
nsmb_dev_close(dev_t dev, int flag, int fmt, struct proc *p)
{
int error;
lck_mtx_lock(dev_lck);
error = nsmb_dev_close_nolock(dev, flag, fmt, p);
lck_mtx_unlock(dev_lck);
return (error);
}
static int nsmb_dev_ioctl(dev_t dev, u_long cmd, caddr_t data, int flag,
struct proc *p)
{
#pragma unused(flag, p)
struct smb_dev *sdp;
struct smb_vc *vcp;
u_int32_t error = 0;
vfs_context_t context;
SMB_CHECKMINOR(sdp, dev);
if ((sdp->sd_flags & NSMBFL_OPEN) == 0)
return (EBADF);
context = vfs_context_create((vfs_context_t)0);
switch (cmd) {
case SMBIOC_NEGOTIATE:
{
struct smbioc_negotiate * vspec = (struct smbioc_negotiate *)data;
if (vspec->ioc_version != SMB_IOC_STRUCT_VERSION) {
error = EINVAL;
} else if (sdp->sd_vc || sdp->sd_share) {
error = EISCONN;
} else {
error = smb_usr_negotiate(vspec, context, sdp);
}
break;
}
case SMBIOC_SSNSETUP:
{
struct smbioc_setup * sspec = (struct smbioc_setup *)data;
if (sspec->ioc_version != SMB_IOC_STRUCT_VERSION) {
error = EINVAL;
} else if (sdp->sd_share) {
error = EISCONN;
} else if (!sdp->sd_vc) {
error = ENOTCONN;
} else {
error = smb_sm_ssnsetup(sdp->sd_vc, sspec, context);
}
break;
}
case SMBIOC_TCON:
{
struct smbioc_share * shspec = (struct smbioc_share *)data;
if (shspec->ioc_version != SMB_IOC_STRUCT_VERSION) {
error = EINVAL;
} else if (sdp->sd_share) {
error = EISCONN;
} else if (!sdp->sd_vc) {
error = ENOTCONN;
} else {
error = smb_sm_tcon(sdp->sd_vc, shspec, &sdp->sd_share, context);
}
break;
}
case SMBIOC_TDIS:
{
struct smbioc_share * shspec = (struct smbioc_share *)data;
if (shspec->ioc_version != SMB_IOC_STRUCT_VERSION) {
error = EINVAL;
} else if (sdp->sd_share == NULL) {
error = ENOTCONN;
} else {
smb_share_rele(sdp->sd_share, context);
sdp->sd_share = NULL;
error = 0;
}
break;
}
case SMBIOC_GET_VC_FLAGS:
{
if (!sdp->sd_vc) {
error = ENOTCONN;
} else {
vcp = sdp->sd_vc;
*(u_int32_t *)data = vcp->vc_flags;
}
break;
}
case SMBIOC_GET_OS_LANMAN:
{
if (!sdp->sd_vc) {
error = ENOTCONN;
} else {
struct smbioc_os_lanman * OSLanman = (struct smbioc_os_lanman *)data;
vcp = sdp->sd_vc;
if (vcp->NativeOS)
strlcpy(OSLanman->NativeOS, vcp->NativeOS, sizeof(OSLanman->NativeOS));
if (vcp->NativeLANManager)
strlcpy(OSLanman->NativeLANManager, vcp->NativeLANManager, sizeof(OSLanman->NativeLANManager));
}
break;
}
case SMBIOC_GET_VC_FLAGS2:
{
if (!sdp->sd_vc) {
error = ENOTCONN;
} else {
vcp = sdp->sd_vc;
*(u_int16_t *)data = vcp->vc_hflags2;
}
break;
}
case SMBIOC_SESSSTATE:
{
if (sdp->sd_vc && (SMB_TRAN_FATAL(sdp->sd_vc, 0) == 0))
*(u_int16_t *)data = EISCONN;
else
*(u_int16_t *)data = ENOTCONN;
break;
}
case SMBIOC_CANCEL_SESSION:
{
sdp->sd_flags |= NSMBFL_CANCEL;
break;
}
case SMBIOC_REQUEST:
{
struct smbioc_rq * dp = (struct smbioc_rq *)data;
if (dp->ioc_version != SMB_IOC_STRUCT_VERSION) {
error = EINVAL;
}
else if (sdp->sd_share == NULL) {
error = ENOTCONN;
} else {
error = smb_usr_simplerequest(sdp->sd_share, dp, context);
}
break;
}
case SMBIOC_T2RQ:
{
struct smbioc_t2rq * dp2 = (struct smbioc_t2rq *)data;
if (dp2->ioc_version != SMB_IOC_STRUCT_VERSION) {
error = EINVAL;
} else if (sdp->sd_share == NULL) {
error = ENOTCONN;
} else {
error = smb_usr_t2request(sdp->sd_share, dp2, context);
}
break;
}
case SMBIOC_READ:
case SMBIOC_WRITE:
{
struct smbioc_rw *rwrq = (struct smbioc_rw *)data;
if (rwrq->ioc_version != SMB_IOC_STRUCT_VERSION) {
error = EINVAL;
} else if (sdp->sd_share == NULL) {
error = ENOTCONN;
} else {
uio_t auio = NULL;
if (vfs_context_is64bit(context))
auio = uio_create(1, rwrq->ioc_offset, UIO_USERSPACE64,
(cmd == SMBIOC_READ) ? UIO_READ : UIO_WRITE);
else {
rwrq->ioc_kern_base = CAST_USER_ADDR_T(rwrq->ioc_base);
auio = uio_create(1, rwrq->ioc_offset, UIO_USERSPACE32,
(cmd == SMBIOC_READ) ? UIO_READ : UIO_WRITE);
}
if (auio) {
smbfh fh;
uio_addiov(auio, rwrq->ioc_kern_base, rwrq->ioc_cnt);
fh = htoles(rwrq->ioc_fh);
if (cmd == SMBIOC_READ)
error = smb_read(sdp->sd_share, fh, auio, context);
else
error = smb_write(sdp->sd_share, fh, auio, context, SMBWRTTIMO);
rwrq->ioc_cnt -= (int32_t)uio_resid(auio);
uio_free(auio);
} else
error = ENOMEM;
}
break;
}
default:
{
error = ENODEV;
break;
}
}
vfs_context_rele(context);
return (error);
}
static int nsmb_dev_load(module_t mod, int cmd, void *arg)
{
#pragma unused(mod, arg)
int error = 0;
lck_mtx_lock(dev_lck);
switch (cmd) {
case MOD_LOAD:
error = smb_sm_init();
if (error)
break;
error = smb_iod_init();
if (error) {
(void)smb_sm_done();
break;
}
if (smb_major == -1) {
dev_t dev;
struct smb_dev *sdp;
smb_major = cdevsw_add(-1, &nsmb_cdevsw);
if (smb_major == -1) {
error = EBUSY;
SMBERROR("smb: cdevsw_add");
(void)smb_iod_done();
(void)smb_sm_done();
}
sdp = malloc(sizeof(*sdp), M_NSMBDEV, M_WAITOK);
bzero(sdp, sizeof(*sdp));
dev = makedev(smb_major, 0);
sdp->sd_devfs = devfs_make_node(dev, DEVFS_CHAR, UID_ROOT, GID_WHEEL, 0666, "nsmb0");
if (!sdp->sd_devfs) {
error = ENOMEM;
SMBERROR("smb: devfs_make_node 0666");
(void)cdevsw_remove(smb_major, &nsmb_cdevsw);
free(sdp, M_NSMBDEV);
(void)smb_iod_done();
(void)smb_sm_done();
}
smb_minor_hiwat = 0;
SMB_GETDEV(dev) = sdp;
}
SMBDEBUG("netsmb_dev: loaded\n");
break;
case MOD_UNLOAD:
smb_iod_done();
error = smb_sm_done();
if (error)
break;
if (smb_major != -1) {
int m;
struct smb_dev *sdp;
for (m = 0; m <= smb_minor_hiwat; m++)
if ((sdp = SMB_GETDEV(m))) {
SMB_GETDEV(m) = 0;
if (sdp->sd_devfs)
devfs_remove(sdp->sd_devfs);
free(sdp, M_NSMBDEV);
}
smb_minor_hiwat = -1;
smb_major = cdevsw_remove(smb_major, &nsmb_cdevsw);
if (smb_major == -1)
SMBERROR("smb: cdevsw_remove failed");
smb_major = -1;
}
SMBDEBUG("netsmb_dev: unloaded\n");
break;
default:
error = EINVAL;
break;
}
lck_mtx_unlock(dev_lck);
return (error);
}
DEV_MODULE(dev_netsmb, nsmb_dev_load, 0);
int
smb_dev2share(int fd, struct smb_share **sspp)
{
vnode_t vp;
struct smb_dev *sdp;
struct smb_share *ssp;
dev_t dev = NODEV;
int error;
error = file_vnode_withvid(fd, &vp, NULL);
if (error)
return (error);
if (vp)
dev = vn_todev(vp);
if (dev == NODEV) {
file_drop(fd);
return (EBADF);
}
SMB_CHECKMINOR(sdp, dev);
ssp = sdp->sd_share;
if (ssp == NULL) {
file_drop(fd);
return (ENOTCONN);
}
sdp->sd_share = NULL;
*sspp = ssp;
file_drop(fd);
return (0);
}