mirror of
https://github.com/sheumann/smbfst.git
synced 2025-11-02 23:56:46 -08:00
290 lines
9.2 KiB
C
290 lines
9.2 KiB
C
/*
|
|
* Copyright (c) 2024 Stephen Heumann
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#define USE_BLANK_SEG
|
|
#include "defs.h"
|
|
#include <memory.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <gsos.h>
|
|
#include <orca.h>
|
|
#include "rpc/rpc.h"
|
|
#include "utils/guidutils.h"
|
|
|
|
// UUID and version for NDR with MS extensions (see C706 and [MS-RPCE])
|
|
#define NDR_SYNTAX {.if_uuid = GUID(8a885d04,1ceb,11c9,9fe8,08002b104860), 2}
|
|
|
|
static IORecGS ioRec = {
|
|
.pCount = 4
|
|
};
|
|
|
|
/*
|
|
* We should be able to use MustRecvFragSize bytes (i.e. 1432), but recent
|
|
* Samba versions want at least 2048 and older Samba versions want 4280
|
|
* (matching Windows), so we go with 4280 for compatibility.
|
|
*/
|
|
char ioBuf[4280];
|
|
|
|
bool ConnectRPC(RPCConnection *conn, char *name) {
|
|
static GSString32 gsName;
|
|
static OpenRecGS openRec = {
|
|
.pCount = 3,
|
|
.pathname = (void*)&gsName,
|
|
.requestAccess = readWriteEnable
|
|
};
|
|
|
|
if (strlen(name) > sizeof(gsName.text))
|
|
return false;
|
|
strcpy(gsName.text, name);
|
|
gsName.length = strlen(name);
|
|
|
|
OpenGS(&openRec);
|
|
if (toolerror())
|
|
return false;
|
|
|
|
conn->refNum = openRec.refNum;
|
|
conn->callId = 1;
|
|
|
|
return true;
|
|
}
|
|
|
|
void DisconnectRPC(RPCConnection *conn) {
|
|
static RefNumRecGS closeRec = {
|
|
.pCount = 1
|
|
};
|
|
|
|
closeRec.refNum = conn->refNum;
|
|
CloseGS(&closeRec);
|
|
}
|
|
|
|
bool SendRPCData(RPCConnection *conn, void *buffer, uint16_t size) {
|
|
ioRec.refNum = conn->refNum;
|
|
ioRec.dataBuffer = buffer;
|
|
ioRec.requestCount = size;
|
|
WriteGS(&ioRec);
|
|
if (toolerror() || ioRec.transferCount != size)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
uint32_t ReceiveRPCMessage(RPCConnection *conn, void *buffer, uint32_t bufSize)
|
|
{
|
|
if (bufSize < sizeof(rpc_common_header_t))
|
|
return 0;
|
|
|
|
ioRec.refNum = conn->refNum;
|
|
ioRec.dataBuffer = buffer;
|
|
ioRec.requestCount = sizeof(rpc_common_header_t);
|
|
ReadGS(&ioRec);
|
|
if (toolerror())
|
|
return 0;
|
|
if (ioRec.transferCount != ioRec.requestCount)
|
|
return 0;
|
|
|
|
#define header (*(rpc_common_header_t*)buffer)
|
|
if (header.rpc_vers != RPC_CO_MAJOR_VERSION)
|
|
return 0;
|
|
if (header.frag_length < sizeof(rpc_common_header_t))
|
|
return 0;
|
|
if (header.frag_length > bufSize)
|
|
return 0;
|
|
|
|
ioRec.dataBuffer = (char*)buffer + sizeof(rpc_common_header_t);
|
|
ioRec.requestCount = header.frag_length - sizeof(rpc_common_header_t);
|
|
ReadGS(&ioRec);
|
|
if (toolerror())
|
|
return 0;
|
|
if (ioRec.transferCount != ioRec.requestCount)
|
|
return 0;
|
|
|
|
return header.frag_length;
|
|
#undef header
|
|
}
|
|
|
|
uint32_t RPCBind(RPCConnection *conn, const p_syntax_id_t *abstractSyntax) {
|
|
uint32_t callId;
|
|
uint32_t msgSize;
|
|
|
|
/*
|
|
* DCE/RPC bind call
|
|
*
|
|
* There seems to be some confusion about the meaning of max_xmit_frag and
|
|
* max_recv_frag in bind calls. I believe max_xmit_frag is supposed to
|
|
* limit the size of fragments sent from the client to the server, and
|
|
* max_recv_frag is supposed to limit the size of fragments sent from the
|
|
* server to the client. However, Solaris and illumos appear to interpret
|
|
* these in the opposite way. To avoid problems, we just set them both to
|
|
* the size of our receive buffer. This has the effect of limiting the
|
|
* size of fragments we can send as well as receive, but that is not a
|
|
* problem, because we never send anything larger than MustRecvFragSize.
|
|
*/
|
|
static rpcconn_bind_hdr_t bindMsg = {
|
|
.hdr.rpc_vers = RPC_CO_MAJOR_VERSION,
|
|
.hdr.rpc_vers_minor = 0,
|
|
.hdr.PTYPE = PTYPE_bind,
|
|
.hdr.pfc_flags = PFC_FIRST_FRAG | PFC_LAST_FRAG,
|
|
.hdr.packed_drep[0] = DREP_LITTLE_ENDIAN | DREP_ASCII,
|
|
.hdr.packed_drep[1] = DREP_IEEE,
|
|
.hdr.packed_drep[2] = 0,
|
|
.hdr.packed_drep[3] = 0,
|
|
.hdr.frag_length = sizeof(bindMsg),
|
|
.hdr.auth_length = 0,
|
|
.hdr.call_id = 0, // set dynamically
|
|
.max_xmit_frag = sizeof(ioBuf),
|
|
.max_recv_frag = sizeof(ioBuf),
|
|
.assoc_group_id = 0,
|
|
.p_context_elem.n_context_elem = 1,
|
|
.p_context_elem.reserved = 0,
|
|
.p_context_elem.reserved2 = 0,
|
|
.p_context_elem.p_cont_elem_1.p_cont_id = 0,
|
|
.p_context_elem.p_cont_elem_1.n_transfer_syn = 1,
|
|
.p_context_elem.p_cont_elem_1.reserved = 0,
|
|
.p_context_elem.p_cont_elem_1.abstract_syntax = {0}, // set dynamically
|
|
.p_context_elem.p_cont_elem_1.transfer_syntax_1 = NDR_SYNTAX,
|
|
};
|
|
|
|
callId = conn->callId++;
|
|
bindMsg.hdr.call_id = callId;
|
|
bindMsg.p_context_elem.p_cont_elem_1.abstract_syntax = *abstractSyntax;
|
|
|
|
if (!SendRPCData(conn, &bindMsg, sizeof(bindMsg)))
|
|
return 0;
|
|
|
|
msgSize = ReceiveRPCMessage(conn, ioBuf, sizeof(ioBuf));
|
|
|
|
if (msgSize < sizeof(rpcconn_bind_ack_hdr_t))
|
|
return 0;
|
|
|
|
#define replyHeader (*(rpc_common_header_t*)ioBuf)
|
|
if (replyHeader.rpc_vers != 5 || replyHeader.rpc_vers_minor != 0)
|
|
return 0;
|
|
if (replyHeader.PTYPE != PTYPE_bind_ack)
|
|
return 0;
|
|
if (replyHeader.packed_drep[0] != DREP_LITTLE_ENDIAN | DREP_ASCII)
|
|
return 0;
|
|
if (replyHeader.call_id != callId)
|
|
return 0;
|
|
if (replyHeader.frag_length != msgSize)
|
|
return 0;
|
|
|
|
#define reply (*(rpcconn_bind_ack_hdr_t*)ioBuf)
|
|
if (reply.max_xmit_frag > sizeof(ioBuf))
|
|
return 0;
|
|
if (reply.max_recv_frag < MustRecvFragSize)
|
|
return 0;
|
|
return reply.assoc_group_id;
|
|
|
|
#undef reply
|
|
#undef replyHeader
|
|
}
|
|
|
|
/*
|
|
* Send an RPC request and get a response.
|
|
*
|
|
* This does not fragment the request, so it must fit within the fragment
|
|
* size limits (potentially as low as MustRecvFragSize, including header).
|
|
*
|
|
* If there is an error, this returns NULL. Otherwise, it returns a locked
|
|
* handle containing the stub data from the responses (reassembled from
|
|
* fragmented packets, if necessary). It is the caller's responsibility to
|
|
* dispose of the handle when done with it.
|
|
*/
|
|
Handle RPCRequest(RPCConnection *conn, uint16_t opnum,
|
|
void *stubData, uint16_t requestStubSize) {
|
|
uint32_t callId;
|
|
uint32_t msgSize;
|
|
Handle dataHandle;
|
|
uint32_t dataSize, newDataSize;
|
|
|
|
static rpcconn_request_hdr_t requestHeader = {
|
|
.hdr.rpc_vers = RPC_CO_MAJOR_VERSION,
|
|
.hdr.rpc_vers_minor = 0,
|
|
.hdr.PTYPE = PTYPE_request,
|
|
.hdr.pfc_flags = PFC_FIRST_FRAG | PFC_LAST_FRAG,
|
|
.hdr.packed_drep[0] = DREP_LITTLE_ENDIAN | DREP_ASCII,
|
|
.hdr.packed_drep[1] = DREP_IEEE,
|
|
.hdr.packed_drep[2] = 0,
|
|
.hdr.packed_drep[3] = 0,
|
|
.hdr.frag_length = 0, // set dynamically
|
|
.hdr.auth_length = 0,
|
|
.hdr.call_id = 0, // set dynamically
|
|
.alloc_hint = 0,
|
|
.p_cont_id = 0,
|
|
.opnum = 0, // set dynamically
|
|
};
|
|
|
|
callId = conn->callId++;
|
|
requestHeader.hdr.call_id = callId;
|
|
requestHeader.hdr.frag_length = sizeof(requestHeader) + requestStubSize;
|
|
requestHeader.opnum = opnum;
|
|
|
|
if (!SendRPCData(conn, &requestHeader, sizeof(requestHeader)))
|
|
return NULL;
|
|
if (!SendRPCData(conn, stubData, requestStubSize))
|
|
return NULL;
|
|
|
|
dataSize = 0;
|
|
dataHandle = NewHandle(0, userid(), attrNoSpec, 0);
|
|
|
|
do {
|
|
msgSize = ReceiveRPCMessage(conn, ioBuf, sizeof(ioBuf));
|
|
|
|
if (msgSize < sizeof(rpcconn_response_hdr_t))
|
|
goto error;
|
|
|
|
#define replyHeader (*(rpc_common_header_t*)ioBuf)
|
|
if (replyHeader.rpc_vers != 5 || replyHeader.rpc_vers_minor != 0)
|
|
goto error;
|
|
if (replyHeader.PTYPE != PTYPE_response)
|
|
goto error;
|
|
if (replyHeader.packed_drep[0] != DREP_LITTLE_ENDIAN | DREP_ASCII)
|
|
goto error;
|
|
if (replyHeader.call_id != callId)
|
|
goto error;
|
|
if (replyHeader.frag_length != msgSize)
|
|
goto error;
|
|
if (replyHeader.auth_length
|
|
> replyHeader.frag_length - sizeof(rpcconn_response_hdr_t))
|
|
goto error;
|
|
|
|
#define reply (*(rpcconn_response_hdr_t*)ioBuf)
|
|
newDataSize = replyHeader.frag_length - sizeof(rpcconn_response_hdr_t)
|
|
- replyHeader.auth_length;
|
|
|
|
HUnlock(dataHandle);
|
|
SetHandleSize(dataSize + newDataSize, dataHandle);
|
|
if (toolerror())
|
|
goto error;
|
|
|
|
HLock(dataHandle);
|
|
memcpy(*dataHandle + dataSize, reply.stub_data, newDataSize);
|
|
dataSize += newDataSize;
|
|
} while (!(replyHeader.pfc_flags & PFC_LAST_FRAG));
|
|
|
|
if (dataSize == 0)
|
|
goto error;
|
|
|
|
return dataHandle;
|
|
|
|
error:
|
|
DisposeHandle(dataHandle);
|
|
return NULL;
|
|
|
|
#undef reply
|
|
#undef replyHeader
|
|
}
|