diff options
Diffstat (limited to 'usr/src/uts/common/fs/smbsrv/smb_rpc.c')
-rw-r--r-- | usr/src/uts/common/fs/smbsrv/smb_rpc.c | 532 |
1 files changed, 532 insertions, 0 deletions
diff --git a/usr/src/uts/common/fs/smbsrv/smb_rpc.c b/usr/src/uts/common/fs/smbsrv/smb_rpc.c new file mode 100644 index 0000000000..394c5addbe --- /dev/null +++ b/usr/src/uts/common/fs/smbsrv/smb_rpc.c @@ -0,0 +1,532 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This module provides a set of wrapper functions to interface to the + * RPC layer. Although this interface was originally implemented as a + * single transaction using an input buffer and an output buffer, it + * turns out that it really should have been a stream/pipe interface. + * This was first discovered when we noticed that Windows2000 was using + * smb_rpc_write and smb_rpc_read instead of smb_rpc_transact and then + * later when we tried to return a larger number of shares than would + * fit in a single transaction buffer. + * + * The interface is still limited by the buffers passed between this + * module and the RPC module but now it will support a buffer overflow + * and allow the client to read the remaining data on subsequent + * requests. Also note that the smb_rpc_write and smb_rpc_read calls + * are basically emulating the smb_rpc_transact function. + */ + +#include <sys/ksynch.h> +#include <smbsrv/smb_incl.h> +#include <smbsrv/mlsvc.h> + + +/* + * This is the list of well-known RPC named pipes that we support. + * The full pipe path will be in the form \\PIPE\\SERVICE. The first + * part can be assumed, so all we need here are the service names. + */ +static char *rpc_named_pipes[] = { + "\\LSARPC", + "\\NETLOGON", + "\\SAMR", + "\\SPOOLSS", + "\\SRVSVC", + "\\SVCCTL", + "\\WINREG", + "\\WKSSVC", + "\\EVENTLOG" +}; + + +/* + * This is a list of the port addresses for the named pipes above. + * We need to check that these are correct but nothing appears to + * rely on them so this is low priority. + */ +#if 0 +static char *rpc_np_ports[] = { + "\\PIPE\\", + "\\PIPE\\lsass", + "\\PIPE\\lsass", + "\\PIPE\\spoolss", + "\\PIPE\\ntsvcs", + "\\PIPE\\ntsvcs", + "\\PIPE\\winreg", + "\\PIPE\\ntsvcs", + "\\PIPE\\ntsvcs" +}; +#endif + + +static int smb_rpc_initialize(struct smb_request *sr, char *pipe_name); +static uint32_t smb_rpc_fid(void); + + +/* + * Named pipe I/O is serialized to ensure that each request has exclusive + * access to the in and out pipe data for the duration of the request. + */ +static void +smb_rpc_enter(mlsvc_pipe_t *pi) +{ + mutex_enter(&pi->mutex); + + while (pi->busy) + cv_wait(&pi->cv, &pi->mutex); + + pi->busy = 1; + mutex_exit(&pi->mutex); +} + +static void +smb_rpc_exit(mlsvc_pipe_t *pi) +{ + mutex_enter(&pi->mutex); + pi->busy = 0; + cv_signal(&pi->cv); + mutex_exit(&pi->mutex); +} + +/* + * smb_rpc_lookup + * + * Lookup a path to see if it's a well-known RPC named pipe. + * + * Returns a pointer to the pipe name (without any leading \'s) if the path + * refers to a well-known RPC named pipe. Otherwise returns a null pointer. + */ +char * +smb_rpc_lookup(char *path) +{ + int i; + char *pipe_name; + + if (path == 0) { + cmn_err(CE_WARN, "smb_rpc_lookup: invalid parameter"); + return (0); + } + + /* + * Skip past the static part of the pipe + * name if it appears in the path. + */ + if (utf8_strncasecmp(path, "\\PIPE\\", 6) == 0) + path += 5; + + for (i = 0; + i < sizeof (rpc_named_pipes) / sizeof (rpc_named_pipes[0]); + ++i) { + if (utf8_strcasecmp(path, rpc_named_pipes[i]) == 0) { + pipe_name = rpc_named_pipes[i]; + pipe_name += strspn(pipe_name, "\\"); + + return (pipe_name); + } + } + return (0); +} + +/* + * smb_rpc_open + * + * Open a well-known RPC named pipe. This routine should be called if + * a file open is requested on a share of type STYPE_IPC. If we + * recognize the pipe, we initialize the session data. This will setup + * a new ofile and insert it into the session file list. + * + * Returns 0 on success, Otherwise an NT status is returned to indicate + * an error. + */ +int +smb_rpc_open(struct smb_request *sr) +{ + struct open_param *op; + char *pipe_name; + int status; + + if (smb_winpipe_open() != 0) + return (NT_STATUS_INTERNAL_ERROR); + + op = &sr->arg.open; + + if ((pipe_name = smb_rpc_lookup(op->fqi.path)) != 0) { + if ((status = smb_rpc_initialize(sr, pipe_name)) != 0) + return (status); + + return (NT_STATUS_SUCCESS); + } + + return (NT_STATUS_OBJECT_NAME_NOT_FOUND); +} + + +/* + * smb_rpc_initialize + * + * Initialize various parts of the session data for a named pipe: open + * parameters and the ofile. There are a number of magic numbers in + * here that we need to identify but largely these values are ignored + * by the rest of the code. Insert the ofile into the session file list. + * + * Returns 0 on success, Otherwise an NT status is returned to indicate + * an error. + */ +static int +smb_rpc_initialize(struct smb_request *sr, char *pipe_name) +{ + struct open_param *op; + struct smb_ofile *of; + smb_error_t err; + + op = &sr->arg.open; + of = smb_ofile_open(sr->tid_tree, NULL, sr->smb_pid, + op->desired_access, 0, op->share_access, + SMB_FTYPE_MESG_PIPE, pipe_name, smb_rpc_fid(), &err); + if (of == NULL) + return (err.status); + + op->dsize = 0x01000; + op->utime.tv_sec = 0; + op->utime.tv_nsec = 0; + op->dattr = SMB_FA_NORMAL; + op->ftype = SMB_FTYPE_MESG_PIPE; + op->action_taken = SMB_OACT_LOCK | SMB_OACT_OPENED; /* 0x8001 */ + op->devstate = SMB_PIPE_READMODE_MESSAGE + | SMB_PIPE_TYPE_MESSAGE + | SMB_PIPE_UNLIMITED_INSTANCES; /* 0x05ff */ + op->fileid = of->f_fid; + op->create_options = 0; + + sr->smb_fid = of->f_fid; + sr->fid_ofile = of; + return (0); +} + +/* + * smb_rpc_transact + * + * This is the entry point for RPC transactions to provide a wrapper for + * the RPC layer. The SMB decoding and encoding is handled here so that + * the RPC layer doesn't have to deal with it. Both bind operations and + * RPC requests are handled here. The connection_fid is an arbitrary id + * used to associate RPC requests with a particular binding handle. + * + * The RPC library expects the input stream to contain the request data. + * It will build the output stream. + * + * If the data to be returned is larger than the client expects, we + * return as much as the client can handle and report a buffer overflow + * warning to inform the client that we have more data to return. The + * residual data remains in the output stream until the client claims + * it or closes the pipe. + */ +int +smb_rpc_transact(struct smb_request *sr, struct uio *uio) +{ + struct smb_xa *xa; + mlsvc_pipe_t *pipe_info; + mlsvc_stream_t *streamin; + struct mbuf *mhead; + int mdrcnt; + int nbytes; + int rc; + + ASSERT(sr->fid_ofile); + ASSERT(sr->fid_ofile->f_ftype == SMB_FTYPE_MESG_PIPE); + ASSERT(sr->fid_ofile->f_pipe_info != NULL); + + xa = sr->r_xa; + mdrcnt = xa->smb_mdrcnt; + pipe_info = sr->fid_ofile->f_pipe_info; + + smb_rpc_enter(pipe_info); + + if (pipe_info->fid == 0) { + smb_rpc_exit(pipe_info); + smbsr_raise_cifs_error(sr, NT_STATUS_INVALID_HANDLE, + ERRDOS, ERROR_INVALID_HANDLE); + /* NOTREACHED */ + } + + streamin = &pipe_info->input; + streamin->uio.uio_iov = uio->uio_iov; + streamin->uio.uio_iovcnt = uio->uio_iovcnt; + streamin->uio.uio_offset = 0; + streamin->uio.uio_resid = uio->uio_resid; + streamin->uio.uio_segflg = UIO_SYSSPACE; + + nbytes = mdrcnt; + + rc = smb_winpipe_call(sr, pipe_info, streamin, SMB_RPC_TRANSACT, + (uint32_t *)&nbytes); + + if (rc != 0) { + smb_rpc_exit(pipe_info); + smbsr_raise_nt_error(sr, + NT_STATUS_CLIENT_SERVER_PARAMETERS_INVALID); + /* NOTREACHED */ + } + + /* + * We need to zero the input stream so that we don't try to + * flush it on close: the mbuf chain belongs to the SMB XA. + * Then reassign the stream to refer to the output/response. + */ + if (nbytes > mdrcnt) { + /* + * We have more data to return than the client expects in the + * response to this request. So we send as much as the client + * can handle, mdrcnt, and store the rest in the output chain. + * The buffer overflow warning informs the client that we + * have more data to send. Typically, the client will call + * SmbRead&X, which will call smb_rpc_read, to get the data. + */ + + mhead = smb_mbuf_get(pipe_info->output, mdrcnt); + xa->rep_data_mb.max_bytes = mdrcnt; + MBC_ATTACH_MBUF(&xa->rep_data_mb, mhead); + + if (sr->session->capabilities & CAP_STATUS32) + smbsr_setup_nt_status(sr, ERROR_SEVERITY_WARNING, + NT_STATUS_BUFFER_OVERFLOW); + else { + sr->smb_rcls = ERRDOS; + sr->smb_err = ERRmoredata; + } + } else { + /* + * The client has provided enough buffer space, all + * we have to do is attach the output stream to the + * transaction response and zero out the stream. + */ + if (nbytes != 0) { + mhead = smb_mbuf_get(pipe_info->output, nbytes); + xa->rep_data_mb.max_bytes = nbytes; + MBC_ATTACH_MBUF(&xa->rep_data_mb, mhead); + } + } + + if (pipe_info->output) { + kmem_free(pipe_info->output, pipe_info->outlen); + pipe_info->output = NULL; + pipe_info->outlen = 0; + } + + smb_rpc_exit(pipe_info); + return (SDRC_NORMAL_REPLY); +} + + +/* + * smb_rpc_fid + * + * The connection_fid is an arbitrary id used to associate RPC requests + * with a particular binding handle. This routine provides a new fid on + * each call. It will not assign 0 or -1 so that those values can + * remain available as sentinels. + */ +static uint32_t +smb_rpc_fid(void) +{ + static uint32_t connection_fid; + static kmutex_t smb_rpc_fid_mutex; + + mutex_enter(&smb_rpc_fid_mutex); + + if (connection_fid == 0) + connection_fid = lbolt << 11; + + do { + ++connection_fid; + } while (connection_fid == 0 || connection_fid == (uint32_t)-1); + + mutex_exit(&smb_rpc_fid_mutex); + + return (connection_fid); +} + + +/* + * smb_rpc_close + * + * This function should be called whenever an IPC file/pipe is closed. + * All remaining I/O is flushed and the RPC layer is informed so that + * it can release the resources being used for this connection. + */ +void +smb_rpc_close(struct smb_ofile *of) +{ + mlsvc_pipe_t *pipe_info; + uint32_t nbytes = 0; + + ASSERT(of); + ASSERT(of->f_ftype == SMB_FTYPE_MESG_PIPE); + ASSERT(of->f_pipe_info != NULL); + + pipe_info = of->f_pipe_info; + smb_rpc_enter(pipe_info); + + if (pipe_info->fid != 0) { + (void) smb_winpipe_call(0, pipe_info, 0, SMB_RPC_FLUSH, + &nbytes); + pipe_info->fid = 0; + } + + if (pipe_info->output) { + kmem_free(pipe_info->output, pipe_info->outlen); + pipe_info->output = NULL; + pipe_info->outlen = 0; + } + + smb_rpc_exit(pipe_info); + + cv_destroy(&pipe_info->cv); + mutex_destroy(&pipe_info->mutex); +} + +/* + * smb_rpc_write + * + * This interface is an alternative to smb_rpc_transact. We set up the + * connection fid, as required, and copy the input data to the input + * stream. The input stream is created by allocating enough mbufs to + * hold the incoming data and doing a uio transfer. It is then up + * to the client to call smb_rpc_read to actually make the transaction + * happen. + * + * Returns 0 on success or an errno on failure. + */ +int +smb_rpc_write(struct smb_request *sr, struct uio *uio) +{ + mlsvc_pipe_t *pipe_info; + mlsvc_stream_t *streamin; + uint32_t mdrcnt; + int rc; + + ASSERT(sr->fid_ofile); + ASSERT(sr->fid_ofile->f_ftype == SMB_FTYPE_MESG_PIPE); + ASSERT(sr->fid_ofile->f_pipe_info != NULL); + + pipe_info = sr->fid_ofile->f_pipe_info; + smb_rpc_enter(pipe_info); + + if (pipe_info->fid == 0) { + smb_rpc_exit(pipe_info); + return (EBADF); + } + + streamin = &pipe_info->input; + streamin->uio.uio_iov = uio->uio_iov; + streamin->uio.uio_iovcnt = uio->uio_iovcnt; + streamin->uio.uio_offset = 0; + streamin->uio.uio_resid = uio->uio_resid; + streamin->uio.uio_segflg = UIO_SYSSPACE; + mdrcnt = (uint32_t)uio->uio_resid; + + rc = smb_winpipe_call(sr, pipe_info, streamin, SMB_RPC_WRITE, + &mdrcnt); + + smb_rpc_exit(pipe_info); + + return ((rc == 0) ? 0 : EIO); +} + +/* + * smb_rpc_read + * + * This interface may be called because smb_rpc_transact could not return + * all of the data in the original transaction or to form the second half + * of a transaction set up using smb_rpc_write. If there is data in the + * output stream, we return it. Otherwise we assume that there is data + * in the input stream that will provide the context to perform an RPC + * transaction. The connection fid (pipe_info->fid) will provide the + * context for mlsvc_rpc_process. + * + * The response data is encoded into raw_data as required by the smb_read + * functions. The uio_resid value indicates the number of bytes read. + */ +/*ARGSUSED*/ +int +smb_rpc_read(struct smb_request *sr, struct uio *uio) +{ + mlsvc_pipe_t *pinfo; + mlsvc_stream_t *streamin; + struct mbuf *mhead; + int mdrcnt; + int nbytes; + int rc = 0; + + ASSERT(sr->fid_ofile); + ASSERT(sr->fid_ofile->f_ftype == SMB_FTYPE_MESG_PIPE); + ASSERT(sr->fid_ofile->f_pipe_info != NULL); + + pinfo = sr->fid_ofile->f_pipe_info; + smb_rpc_enter(pinfo); + + if (pinfo->fid == 0) { + rc = EBADF; + goto smb_rpc_read_exit; + } + + /* + * if there is data left in the outpipe return it now + */ + streamin = 0; + mdrcnt = uio->uio_resid; + nbytes = mdrcnt; + + rc = smb_winpipe_call(sr, pinfo, streamin, SMB_RPC_READ, + (uint32_t *)&nbytes); + + if (rc != 0 || nbytes == 0) { + rc = EIO; + goto smb_rpc_read_exit; + } + if (nbytes > mdrcnt) { + nbytes = mdrcnt; + } + + mhead = smb_mbuf_get(pinfo->output, nbytes); + MBC_SETUP(&sr->raw_data, nbytes); + MBC_ATTACH_MBUF(&sr->raw_data, mhead); + + uio->uio_resid -= nbytes; + +smb_rpc_read_exit: + if (pinfo->output) { + kmem_free(pinfo->output, pinfo->outlen); + pinfo->output = NULL; + pinfo->outlen = 0; + } + + smb_rpc_exit(pinfo); + return (rc); +} |