#include <sys/stat.h>
#include <sys/uio.h>
#include <sys/ksynch.h>
#include <sys/stropts.h>
#include <sys/socket.h>
#include <sys/filio.h>
#include <smbsrv/smb_kproto.h>
#include <smbsrv/smb_xdr.h>
#include <smb/winioctl.h>
static uint32_t smb_opipe_wait(smb_request_t *, smb_fsctl_t *);
static smb_opipe_t *
smb_opipe_alloc(smb_request_t *sr)
{
smb_server_t *sv = sr->sr_server;
smb_opipe_t *opipe;
ksocket_t sock;
if (ksocket_socket(&sock, AF_UNIX, SOCK_STREAM, 0,
KSOCKET_SLEEP, sr->user_cr) != 0)
return (NULL);
opipe = kmem_cache_alloc(smb_cache_opipe, KM_SLEEP);
bzero(opipe, sizeof (smb_opipe_t));
mutex_init(&opipe->p_mutex, NULL, MUTEX_DEFAULT, NULL);
cv_init(&opipe->p_cv, NULL, CV_DEFAULT, NULL);
opipe->p_magic = SMB_OPIPE_MAGIC;
opipe->p_server = sv;
opipe->p_refcnt = 1;
opipe->p_socket = sock;
return (opipe);
}
void
smb_opipe_dealloc(smb_opipe_t *opipe)
{
smb_server_t *sv;
SMB_OPIPE_VALID(opipe);
sv = opipe->p_server;
SMB_SERVER_VALID(sv);
if (opipe->p_socket != NULL)
(void) ksocket_close(opipe->p_socket, zone_kcred());
opipe->p_magic = (uint32_t)~SMB_OPIPE_MAGIC;
cv_destroy(&opipe->p_cv);
mutex_destroy(&opipe->p_mutex);
kmem_cache_free(smb_cache_opipe, opipe);
}
static void
smb_opipe_cancel(smb_request_t *sr)
{
ksocket_t so;
switch (sr->session->s_state) {
case SMB_SESSION_STATE_DISCONNECTED:
case SMB_SESSION_STATE_TERMINATED:
if ((so = sr->cancel_arg2) != NULL)
(void) ksocket_shutdown(so, SHUT_RDWR, sr->user_cr);
break;
}
}
static int
smb_opipe_connect(smb_request_t *sr, smb_opipe_t *opipe)
{
struct sockaddr_un saddr;
smb_arg_open_t *op = &sr->sr_open;
const char *name;
int rc;
name = op->fqi.fq_path.pn_path;
name += strspn(name, "\\");
if (smb_strcasecmp(name, "PIPE", 4) == 0) {
name += 4;
name += strspn(name, "\\");
}
(void) strlcpy(opipe->p_name, name, SMB_OPIPE_MAXNAME);
(void) smb_strlwr(opipe->p_name);
bzero(&saddr, sizeof (saddr));
saddr.sun_family = AF_UNIX;
(void) snprintf(saddr.sun_path, sizeof (saddr.sun_path),
"%s/%s", SMB_PIPE_DIR, opipe->p_name);
rc = ksocket_connect(opipe->p_socket, (struct sockaddr *)&saddr,
sizeof (saddr), sr->user_cr);
return (rc);
}
static int
smb_opipe_exists(char *name)
{
struct sockaddr_un saddr;
vnode_t *vp;
int err;
bzero(&saddr, sizeof (saddr));
saddr.sun_family = AF_UNIX;
(void) snprintf(saddr.sun_path, sizeof (saddr.sun_path),
"%s/%s", SMB_PIPE_DIR, name);
err = lookupname(saddr.sun_path, UIO_SYSSPACE, FOLLOW, NULLVPP, &vp);
if (err == 0) {
VN_RELE(vp);
}
return (err);
}
static void
smb_opipe_send_userinfo(smb_request_t *sr, smb_opipe_t *opipe,
smb_error_t *errp)
{
XDR xdrs;
smb_netuserinfo_t nui;
smb_pipehdr_t phdr;
char *buf;
uint32_t buflen;
uint32_t status;
size_t iocnt = 0;
int rc;
errp->status = NT_STATUS_INTERNAL_ERROR;
smb_user_netinfo_init(sr->uid_user, &nui);
phdr.ph_magic = SMB_PIPE_HDR_MAGIC;
phdr.ph_uilen = xdr_sizeof(smb_netuserinfo_xdr, &nui);
buflen = sizeof (phdr) + phdr.ph_uilen;
buf = kmem_alloc(buflen, KM_SLEEP);
bcopy(&phdr, buf, sizeof (phdr));
xdrmem_create(&xdrs, buf + sizeof (phdr),
buflen - (sizeof (phdr)), XDR_ENCODE);
if (!smb_netuserinfo_xdr(&xdrs, &nui))
goto out;
mutex_enter(&sr->sr_mutex);
if (sr->sr_state != SMB_REQ_STATE_ACTIVE) {
mutex_exit(&sr->sr_mutex);
errp->status = NT_STATUS_CANCELLED;
goto out;
}
sr->sr_state = SMB_REQ_STATE_WAITING_PIPE;
sr->cancel_method = smb_opipe_cancel;
sr->cancel_arg2 = opipe->p_socket;
mutex_exit(&sr->sr_mutex);
rc = ksocket_send(opipe->p_socket, buf, buflen, 0,
&iocnt, sr->user_cr);
if (rc == 0 && iocnt != buflen)
rc = EIO;
if (rc == 0)
rc = ksocket_recv(opipe->p_socket, &status, sizeof (status),
0, &iocnt, sr->user_cr);
if (rc == 0 && iocnt != sizeof (status))
rc = EIO;
mutex_enter(&sr->sr_mutex);
switch_state:
switch (sr->sr_state) {
case SMB_REQ_STATE_WAITING_PIPE:
sr->sr_state = SMB_REQ_STATE_ACTIVE;
break;
case SMB_REQ_STATE_CANCEL_PENDING:
cv_wait(&sr->sr_st_cv, &sr->sr_mutex);
goto switch_state;
case SMB_REQ_STATE_CANCELLED:
rc = EINTR;
break;
default:
break;
}
sr->cancel_method = NULL;
sr->cancel_arg2 = NULL;
mutex_exit(&sr->sr_mutex);
switch (rc) {
case 0:
errp->status = status;
break;
case EINTR:
errp->status = NT_STATUS_CANCELLED;
break;
default:
errp->status = NT_STATUS_PIPE_NOT_AVAILABLE;
break;
}
out:
xdr_destroy(&xdrs);
kmem_free(buf, buflen);
smb_user_netinfo_fini(&nui);
}
int
smb_opipe_open(smb_request_t *sr, smb_ofile_t *ofile)
{
smb_arg_open_t *op = &sr->sr_open;
smb_attr_t *ap = &op->fqi.fq_fattr;
smb_opipe_t *opipe;
smb_error_t err;
opipe = smb_opipe_alloc(sr);
if (opipe == NULL)
return (NT_STATUS_INTERNAL_ERROR);
if (smb_opipe_connect(sr, opipe) != 0) {
smb_opipe_dealloc(opipe);
return (NT_STATUS_OBJECT_NAME_NOT_FOUND);
}
smb_opipe_send_userinfo(sr, opipe, &err);
if (err.status != 0) {
smb_opipe_dealloc(opipe);
return (err.status);
}
if (!smb_tree_is_connected(sr->tid_tree)) {
smb_opipe_dealloc(opipe);
return (NT_STATUS_NETWORK_NAME_DELETED);
}
op->pipe = opipe;
smb_ofile_open(sr, op, ofile);
op->pipe = NULL;
opipe->p_ofile = ofile;
(void) smb_opipe_getattr(ofile, &op->fqi.fq_fattr);
op->dsize = 0;
op->dattr = ap->sa_dosattr;
op->fileid = ap->sa_vattr.va_nodeid;
op->ftype = SMB_FTYPE_MESG_PIPE;
op->action_taken = SMB_OACT_OPLOCK | SMB_OACT_OPENED;
op->devstate = SMB_PIPE_READMODE_MESSAGE
| SMB_PIPE_TYPE_MESSAGE
| SMB_PIPE_UNLIMITED_INSTANCES;
sr->smb_fid = ofile->f_fid;
sr->fid_ofile = ofile;
return (NT_STATUS_SUCCESS);
}
void
smb_opipe_close(smb_ofile_t *of)
{
smb_opipe_t *opipe;
ksocket_t sock;
ASSERT(of->f_state == SMB_OFILE_STATE_CLOSING);
ASSERT(of->f_ftype == SMB_FTYPE_MESG_PIPE);
opipe = of->f_pipe;
SMB_OPIPE_VALID(opipe);
mutex_enter(&opipe->p_mutex);
sock = opipe->p_socket;
opipe->p_socket = NULL;
mutex_exit(&opipe->p_mutex);
(void) ksocket_shutdown(sock, SHUT_RDWR, of->f_cr);
(void) ksocket_close(sock, of->f_cr);
}
int
smb_opipe_write(smb_request_t *sr, struct uio *uio)
{
struct nmsghdr msghdr;
smb_ofile_t *ofile;
smb_opipe_t *opipe;
ksocket_t sock;
size_t sent = 0;
int rc = 0;
ofile = sr->fid_ofile;
ASSERT(ofile->f_ftype == SMB_FTYPE_MESG_PIPE);
opipe = ofile->f_pipe;
SMB_OPIPE_VALID(opipe);
mutex_enter(&opipe->p_mutex);
sock = opipe->p_socket;
if (sock != NULL)
ksocket_hold(sock);
mutex_exit(&opipe->p_mutex);
if (sock == NULL)
return (EBADF);
bzero(&msghdr, sizeof (msghdr));
msghdr.msg_iov = uio->uio_iov;
msghdr.msg_iovlen = uio->uio_iovcnt;
while (uio->uio_resid > 0) {
rc = ksocket_sendmsg(sock, &msghdr, 0, &sent, ofile->f_cr);
if (rc != 0)
break;
uio->uio_resid -= sent;
}
ksocket_rele(sock);
return (rc);
}
int
smb_opipe_read(smb_request_t *sr, struct uio *uio)
{
struct nmsghdr msghdr;
smb_ofile_t *ofile;
smb_opipe_t *opipe;
ksocket_t sock;
size_t recvcnt = 0;
int rc;
ofile = sr->fid_ofile;
ASSERT(ofile->f_ftype == SMB_FTYPE_MESG_PIPE);
opipe = ofile->f_pipe;
SMB_OPIPE_VALID(opipe);
mutex_enter(&opipe->p_mutex);
sock = opipe->p_socket;
if (sock != NULL)
ksocket_hold(sock);
mutex_exit(&opipe->p_mutex);
if (sock == NULL)
return (EBADF);
mutex_enter(&sr->sr_mutex);
if (sr->sr_state != SMB_REQ_STATE_ACTIVE) {
mutex_exit(&sr->sr_mutex);
rc = EINTR;
goto out;
}
sr->sr_state = SMB_REQ_STATE_WAITING_PIPE;
sr->cancel_method = smb_opipe_cancel;
sr->cancel_arg2 = sock;
mutex_exit(&sr->sr_mutex);
bzero(&msghdr, sizeof (msghdr));
msghdr.msg_iov = uio->uio_iov;
msghdr.msg_iovlen = uio->uio_iovcnt;
rc = ksocket_recvmsg(sock, &msghdr, 0,
&recvcnt, ofile->f_cr);
mutex_enter(&sr->sr_mutex);
switch_state:
switch (sr->sr_state) {
case SMB_REQ_STATE_WAITING_PIPE:
sr->sr_state = SMB_REQ_STATE_ACTIVE;
break;
case SMB_REQ_STATE_CANCEL_PENDING:
cv_wait(&sr->sr_st_cv, &sr->sr_mutex);
goto switch_state;
case SMB_REQ_STATE_CANCELLED:
rc = EINTR;
break;
default:
break;
}
sr->cancel_method = NULL;
sr->cancel_arg2 = NULL;
mutex_exit(&sr->sr_mutex);
if (rc != 0)
goto out;
if (recvcnt == 0) {
rc = EPIPE;
goto out;
}
uio->uio_resid -= recvcnt;
out:
ksocket_rele(sock);
return (rc);
}
int
smb_opipe_ioctl(smb_request_t *sr, int cmd, void *arg, int *rvalp)
{
smb_ofile_t *ofile;
smb_opipe_t *opipe;
ksocket_t sock;
int rc;
ofile = sr->fid_ofile;
ASSERT(ofile->f_ftype == SMB_FTYPE_MESG_PIPE);
opipe = ofile->f_pipe;
SMB_OPIPE_VALID(opipe);
mutex_enter(&opipe->p_mutex);
sock = opipe->p_socket;
if (sock != NULL)
ksocket_hold(sock);
mutex_exit(&opipe->p_mutex);
if (sock == NULL)
return (EBADF);
rc = ksocket_ioctl(sock, cmd, (intptr_t)arg, rvalp, ofile->f_cr);
ksocket_rele(sock);
return (rc);
}
int
smb_opipe_getattr(smb_ofile_t *of, smb_attr_t *ap)
{
if (of->f_pipe == NULL)
return (EINVAL);
ap->sa_vattr.va_type = VFIFO;
ap->sa_vattr.va_nlink = 1;
ap->sa_vattr.va_nodeid = (uintptr_t)of->f_pipe;
ap->sa_dosattr = FILE_ATTRIBUTE_NORMAL;
ap->sa_allocsz = SMB_PIPE_MAX_MSGSIZE;
return (0);
}
int
smb_opipe_getname(smb_ofile_t *of, char *buf, size_t buflen)
{
smb_opipe_t *opipe;
if ((opipe = of->f_pipe) == NULL)
return (EINVAL);
(void) snprintf(buf, buflen, "\\%s", opipe->p_name);
return (0);
}
uint32_t
smb_opipe_fsctl(smb_request_t *sr, smb_fsctl_t *fsctl)
{
uint32_t status;
if (!STYPE_ISIPC(sr->tid_tree->t_res_type))
return (NT_STATUS_INVALID_DEVICE_REQUEST);
switch (fsctl->CtlCode) {
case FSCTL_PIPE_TRANSCEIVE:
status = smb_opipe_transceive(sr, fsctl);
break;
case FSCTL_PIPE_PEEK:
status = NT_STATUS_INVALID_DEVICE_REQUEST;
break;
case FSCTL_PIPE_WAIT:
status = smb_opipe_wait(sr, fsctl);
break;
default:
ASSERT(!"CtlCode");
status = NT_STATUS_INTERNAL_ERROR;
break;
}
return (status);
}
uint32_t
smb_opipe_transceive(smb_request_t *sr, smb_fsctl_t *fsctl)
{
smb_vdb_t *vdb;
smb_ofile_t *ofile;
struct mbuf *mb;
uint32_t status;
int len, rc;
ofile = sr->fid_ofile;
if (ofile->f_ftype != SMB_FTYPE_MESG_PIPE)
return (NT_STATUS_INVALID_HANDLE);
vdb = smb_srm_zalloc(sr, sizeof (*vdb));
rc = smb_mbc_decodef(fsctl->in_mbc, "#B",
fsctl->InputCount, vdb);
if (rc != 0) {
return (NT_STATUS_INVALID_PARAMETER);
}
rc = smb_opipe_write(sr, &vdb->vdb_uio);
if (rc != 0)
return (smb_errno2status(rc));
vdb->vdb_tag = 0;
vdb->vdb_uio.uio_iov = &vdb->vdb_iovec[0];
vdb->vdb_uio.uio_iovcnt = MAX_IOVEC;
vdb->vdb_uio.uio_segflg = UIO_SYSSPACE;
vdb->vdb_uio.uio_extflg = UIO_COPY_DEFAULT;
vdb->vdb_uio.uio_loffset = (offset_t)0;
vdb->vdb_uio.uio_resid = fsctl->MaxOutputResp;
mb = smb_mbuf_allocate(&vdb->vdb_uio);
rc = smb_opipe_read(sr, &vdb->vdb_uio);
if (rc != 0) {
m_freem(mb);
return (smb_errno2status(rc));
}
len = fsctl->MaxOutputResp - vdb->vdb_uio.uio_resid;
smb_mbuf_trim(mb, len);
MBC_ATTACH_MBUF(fsctl->out_mbc, mb);
status = NT_STATUS_SUCCESS;
if (fsctl->MaxOutputResp < SMB_PIPE_MAX_MSGSIZE &&
vdb->vdb_uio.uio_resid == 0) {
int nread = 0, trval;
rc = smb_opipe_ioctl(sr, FIONREAD, &nread, &trval);
if (rc == 0 && nread != 0)
status = NT_STATUS_BUFFER_OVERFLOW;
}
return (status);
}
static uint32_t
smb_opipe_wait(smb_request_t *sr, smb_fsctl_t *fsctl)
{
char *name;
uint64_t timeout;
uint32_t namelen;
int rc;
uint8_t tflag;
rc = smb_mbc_decodef(fsctl->in_mbc, "qlb.",
&timeout,
&namelen,
&tflag);
if (rc != 0)
return (NT_STATUS_INVALID_PARAMETER);
rc = smb_mbc_decodef(fsctl->in_mbc, "%#U",
sr,
namelen,
&name);
if (rc != 0)
return (NT_STATUS_INVALID_PARAMETER);
rc = smb_opipe_exists(name);
if (rc != 0)
return (NT_STATUS_OBJECT_NAME_NOT_FOUND);
if (tflag != 0) {
clock_t ticks = MSEC_TO_TICK(timeout * 100);
if (ticks > MSEC_TO_TICK(100))
ticks = MSEC_TO_TICK(100);
delay(ticks);
}
return (0);
}