/* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2020 Tintri by DDN, Inc. All rights reserved. */ /* * ML-RPC Client handle interface and support functions. */ #include #include #include #include #include #include #include #include #include #include static int ndr_xa_init(ndr_client_t *, ndr_xa_t *); static int ndr_xa_exchange(ndr_client_t *, ndr_xa_t *); static int ndr_xa_read(ndr_client_t *, ndr_xa_t *); static void ndr_xa_preserve(ndr_client_t *, ndr_xa_t *); static void ndr_xa_destruct(ndr_client_t *, ndr_xa_t *); static void ndr_xa_release(ndr_client_t *); /* See notes in mlrpc_clh_bind */ int rpc_pipe_open_retries = 10; /* * Create an RPC client binding handle using the given smb_ctx. * That context must already have a session and tree connected. * * Returns zero or an errno value. */ int mlrpc_clh_create(mlrpc_handle_t *handle, void *ctx) { ndr_client_t *clnt = NULL; if (ctx == NULL) return (EINVAL); /* * Allocate... */ if ((clnt = calloc(1, sizeof (*clnt))) == NULL) return (ENOMEM); clnt->xa_fd = -1; /* * Setup the transport functions. * Always a named pipe (for now). */ clnt->xa_private = ctx; clnt->xa_init = ndr_xa_init; clnt->xa_exchange = ndr_xa_exchange; clnt->xa_read = ndr_xa_read; clnt->xa_preserve = ndr_xa_preserve; clnt->xa_destruct = ndr_xa_destruct; clnt->xa_release = ndr_xa_release; /* See _is_bind_handle */ clnt->handle = &handle->handle; ndr_svc_binding_pool_init(&clnt->binding_list, clnt->binding_pool, NDR_N_BINDING_POOL); if ((clnt->heap = ndr_heap_create()) == NULL) goto nomem; /* success! */ bzero(handle, sizeof (*handle)); handle->clnt = clnt; return (0); nomem: free(clnt); return (ENOMEM); } /* * Set up this handle to perform RPC-level authentication. */ uint32_t mlrpc_clh_set_auth(mlrpc_handle_t *handle, ndr_auth_ctx_t *auth_ctx) { ndr_client_t *clnt = NULL; if ((clnt = handle->clnt) == NULL) return (NT_STATUS_INTERNAL_ERROR); if (auth_ctx != NULL) { /* struct copy */ clnt->auth_ctx = *auth_ctx; } return (NT_STATUS_SUCCESS); } /* * This call must be made to initialize an RPC client structure and bind * to the remote service before any RPCs can be exchanged with that service. * * The mlrpc_handle_t is a wrapper that is used to associate an RPC handle * with the client context for an instance of the interface. The handle * is zeroed to ensure that it doesn't look like a valid handle - * handle content is provided by the remove service. * * The client points to this top-level handle so that we know when to * unbind and teardown the connection. As each handle is initialized it * will inherit a reference to the client context. * * * Similar to MSRPC RpcBindingBind() * * Returns 0 or an NT_STATUS: (failed in...) * * RPC_NT_SERVER_TOO_BUSY (open pipe) * RPC_NT_SERVER_UNAVAILABLE (open pipe) * NT_STATUS_ACCESS_DENIED (open pipe) * NT_STATUS_INVALID_PARAMETER (rpc bind) * NT_STATUS_INTERNAL_ERROR (bad args etc) * NT_STATUS_NO_MEMORY */ uint32_t mlrpc_clh_bind(mlrpc_handle_t *handle, ndr_service_t *svc) { ndr_client_t *clnt = NULL; struct smb_ctx *ctx = NULL; uint32_t status = 0; int fd = -1; int rc, retries; if ((clnt = handle->clnt) == NULL) return (NT_STATUS_INTERNAL_ERROR); if ((ctx = clnt->xa_private) == NULL) return (NT_STATUS_INTERNAL_ERROR); if (clnt->xa_fd != -1) return (NT_STATUS_INTERNAL_ERROR); /* * Open the named pipe. * * Sometimes a DC may return NT_STATUS_PIPE_NOT_AVAILABLE for * the first few seconds during service auto-start. The client * translates that to EBUSY, so when we see that, wait a bit * and retry the open for up to rpc_pipe_open_retries. If we * fail even after retries, return RPC_NT_SERVER_TOO_BUSY, * which is how callers of this layer expect that reported. * We try up to 10 times, with a 0.5 sec. wait after each * BUSY failure, giving a total wait here of 5 sec. */ retries = rpc_pipe_open_retries; retry_open: fd = smb_fh_open(ctx, svc->endpoint, O_RDWR); if (fd < 0) { rc = errno; switch (rc) { case EBUSY: if (--retries > 0) { (void) poll(NULL, 0, 500); goto retry_open; } status = RPC_NT_SERVER_TOO_BUSY; break; case EACCES: status = NT_STATUS_ACCESS_DENIED; break; default: status = RPC_NT_SERVER_UNAVAILABLE; break; } return (status); } clnt->xa_fd = fd; /* Paranoia, in case of re-bind. */ bzero(&handle->handle, sizeof (ndr_hdid_t)); /* * Do the OtW RPC bind. */ rc = ndr_clnt_bind(clnt, svc, &clnt->binding); switch (rc) { case NDR_DRC_FAULT_OUT_OF_MEMORY: status = NT_STATUS_NO_MEMORY; break; case NDR_DRC_FAULT_API_SERVICE_INVALID: /* svc->..._uuid parse errors */ status = NT_STATUS_INTERNAL_ERROR; break; default: if (NDR_DRC_IS_FAULT(rc)) { status = RPC_NT_PROTOCOL_ERROR; break; } /* FALLTHROUGH */ case NDR_DRC_OK: status = NT_STATUS_SUCCESS; } if (status != 0) { if (fd != -1) (void) smb_fh_close(fd); clnt->xa_fd = -1; } return (status); } /* * Unbind and close the pipe to an RPC service. * * Similar to MSRPC RpcBindingUnbind() * This should be called after a dropped connection. */ void mlrpc_clh_unbind(mlrpc_handle_t *handle) { ndr_client_t *clnt = handle->clnt; if (clnt->xa_fd != -1) { (void) smb_fh_close(clnt->xa_fd); clnt->xa_fd = -1; } } /* * If the heap has been preserved we need to go through an xa release. * The heap is preserved during an RPC call because that's where data * returned from the server is stored. * * Otherwise we destroy the heap directly. * * Returns the xa_private pointer (if non-NULL) to inform the caller * that it can now be destroyed. */ void * mlrpc_clh_free(mlrpc_handle_t *handle) { ndr_client_t *clnt = handle->clnt; void *private; if (clnt == NULL) return (NULL); /* * Should never get an unbind on inherited handles. * Callers of ndr_inherit_handle() check handles * with ndr_is_bind_handle() before calling this. * * Maybe make this function more tolerant? */ assert(handle->clnt->handle == &handle->handle); mlrpc_clh_unbind(handle); if (clnt->heap_preserved) ndr_clnt_free_heap(clnt); /* xa_release */ else ndr_heap_destroy(clnt->heap); /* * Note: Caller will free the smb_ctx stored in * clnt->xa_private (or possibly reuse it). */ private = clnt->xa_private; free(clnt); bzero(handle, sizeof (*handle)); return (private); } /* * Call the RPC function identified by opnum. The remote service is * identified by the handle, which should have been initialized by * ndr_rpc_bind. * * If the RPC call is successful (returns 0), the caller must call * ndr_rpc_release to release the heap. Otherwise, we release the * heap here. */ int ndr_rpc_call(mlrpc_handle_t *handle, int opnum, void *params) { ndr_client_t *clnt = handle->clnt; int rc; if (ndr_rpc_get_heap(handle) == NULL) return (-1); rc = ndr_clnt_call(clnt->binding, opnum, params); /* * Always clear the nonull flag to ensure * it is not applied to subsequent calls. */ clnt->nonull = B_FALSE; if (NDR_DRC_IS_FAULT(rc)) { ndr_rpc_release(handle); return (-1); } return (0); } /* * Outgoing strings should not be null terminated. */ void ndr_rpc_set_nonull(mlrpc_handle_t *handle) { handle->clnt->nonull = B_TRUE; } /* * Get the session key from a bound RPC client handle. * * The key returned is the 16-byte "user session key" * established by the underlying authentication protocol * (either Kerberos or NTLM). This key is needed for * SAM RPC calls such as SamrSetInformationUser, etc. * See [MS-SAMR] sections: 2.2.3.3, 2.2.7.21, 2.2.7.25. * * Returns zero (success) or an errno. */ int ndr_rpc_get_ssnkey(mlrpc_handle_t *handle, uchar_t *key, size_t len) { ndr_client_t *clnt = handle->clnt; if (clnt == NULL || clnt->xa_fd == -1) return (EINVAL); return (smb_fh_getssnkey(clnt->xa_fd, key, len)); } void * ndr_rpc_malloc(mlrpc_handle_t *handle, size_t size) { ndr_heap_t *heap; if ((heap = ndr_rpc_get_heap(handle)) == NULL) return (NULL); return (ndr_heap_malloc(heap, size)); } ndr_heap_t * ndr_rpc_get_heap(mlrpc_handle_t *handle) { ndr_client_t *clnt = handle->clnt; if (clnt->heap == NULL) clnt->heap = ndr_heap_create(); return (clnt->heap); } /* * Must be called by RPC clients to free the heap after a successful RPC * call, i.e. ndr_rpc_call returned 0. The caller should take a copy * of any data returned by the RPC prior to calling this function because * returned data is in the heap. */ void ndr_rpc_release(mlrpc_handle_t *handle) { ndr_client_t *clnt = handle->clnt; if (clnt->heap_preserved) ndr_clnt_free_heap(clnt); else ndr_heap_destroy(clnt->heap); clnt->heap = NULL; } /* * Returns true if the handle is null. * Otherwise returns false. */ boolean_t ndr_is_null_handle(mlrpc_handle_t *handle) { static const ndr_hdid_t hdid0 = {0}; if (handle == NULL || handle->clnt == NULL) return (B_TRUE); if (!memcmp(&handle->handle, &hdid0, sizeof (hdid0))) return (B_TRUE); return (B_FALSE); } /* * Returns true if the handle is the top level bind handle. * Otherwise returns false. */ boolean_t ndr_is_bind_handle(mlrpc_handle_t *handle) { return (handle->clnt->handle == &handle->handle); } /* * Pass the client reference from parent to child. */ void ndr_inherit_handle(mlrpc_handle_t *child, mlrpc_handle_t *parent) { child->clnt = parent->clnt; } /* * ndr_rpc_status remains in libmlsvc mlsvc_client.c */ /* * The following functions provide the client callback interface. * If the caller hasn't provided a heap, create one here. */ static int ndr_xa_init(ndr_client_t *clnt, ndr_xa_t *mxa) { ndr_stream_t *recv_nds = &mxa->recv_nds; ndr_stream_t *send_nds = &mxa->send_nds; ndr_heap_t *heap = clnt->heap; int rc; if (heap == NULL) { if ((heap = ndr_heap_create()) == NULL) return (-1); clnt->heap = heap; } mxa->heap = heap; rc = nds_initialize(send_nds, 0, NDR_MODE_CALL_SEND, heap); if (rc == 0) rc = nds_initialize(recv_nds, NDR_PDU_SIZE_HINT_DEFAULT, NDR_MODE_RETURN_RECV, heap); if (rc != 0) { nds_destruct(&mxa->recv_nds); nds_destruct(&mxa->send_nds); ndr_heap_destroy(mxa->heap); mxa->heap = NULL; clnt->heap = NULL; return (-1); } if (clnt->nonull) NDS_SETF(send_nds, NDS_F_NONULL); return (0); } /* * This is the entry pointy for an RPC client call exchange with * a server, which will result in an smbrdr SmbTransact request. * * SmbTransact should return the number of bytes received, which * we record as the PDU size, or a negative error code. */ static int ndr_xa_exchange(ndr_client_t *clnt, ndr_xa_t *mxa) { ndr_stream_t *recv_nds = &mxa->recv_nds; ndr_stream_t *send_nds = &mxa->send_nds; int err, more, nbytes; nbytes = recv_nds->pdu_max_size; err = smb_fh_xactnp(clnt->xa_fd, send_nds->pdu_size, (char *)send_nds->pdu_base_offset, &nbytes, (char *)recv_nds->pdu_base_offset, &more); if (err) { recv_nds->pdu_size = 0; return (-1); } recv_nds->pdu_size = nbytes; return (0); } /* * This entry point will be invoked if the xa-exchange response contained * only the first fragment of a multi-fragment response. The RPC client * code will then make repeated xa-read requests to obtain the remaining * fragments, which will result in smbrdr SmbReadX requests. * * SmbReadX should return the number of bytes received, in which case we * expand the PDU size to include the received data, or a negative error * code. */ static int ndr_xa_read(ndr_client_t *clnt, ndr_xa_t *mxa) { ndr_stream_t *nds = &mxa->recv_nds; int len; int nbytes; if ((len = (nds->pdu_max_size - nds->pdu_size)) < 0) return (-1); nbytes = smb_fh_read(clnt->xa_fd, 0, len, (char *)nds->pdu_base_offset + nds->pdu_size); if (nbytes < 0) return (-1); nds->pdu_size += nbytes; if (nds->pdu_size > nds->pdu_max_size) { nds->pdu_size = nds->pdu_max_size; return (-1); } return (nbytes); } /* * Preserve the heap so that the client application has access to data * returned from the server after an RPC call. */ static void ndr_xa_preserve(ndr_client_t *clnt, ndr_xa_t *mxa) { assert(clnt->heap == mxa->heap); clnt->heap_preserved = B_TRUE; mxa->heap = NULL; } /* * Dispose of the transaction streams. If the heap has not been * preserved, we can destroy it here. */ static void ndr_xa_destruct(ndr_client_t *clnt, ndr_xa_t *mxa) { nds_destruct(&mxa->recv_nds); nds_destruct(&mxa->send_nds); if (!clnt->heap_preserved) { ndr_heap_destroy(mxa->heap); mxa->heap = NULL; clnt->heap = NULL; } } /* * Dispose of a preserved heap. */ static void ndr_xa_release(ndr_client_t *clnt) { if (clnt->heap_preserved) { ndr_heap_destroy(clnt->heap); clnt->heap = NULL; clnt->heap_preserved = B_FALSE; } }