mirror of
https://github.com/samba-team/samba.git
synced 2024-12-23 17:34:34 +03:00
s3/rpc_server: don't unmarshall PDUs twice
DCE/RPC PDUs are currently unmarshalled firstly by the generic librpc dcerpc_read_ncacn_packet_[send/recv] functions, and subsequently a second time by the source3 rpc_server, which ignores the unmarshalled packet and re-parses the receive buffer. Signed-off-by: David Disseldorp <ddiss@samba.org> Reviewed-by: Stefan Metzmacher <metze@samba.org> Autobuild-User(master): Stefan Metzmacher <metze@samba.org> Autobuild-Date(master): Wed Dec 11 22:24:31 CET 2013 on sn-devel-104
This commit is contained in:
parent
27baff0ec9
commit
646d8c26f8
@ -432,9 +432,6 @@ void named_pipe_packet_process(struct tevent_req *subreq)
|
||||
DATA_BLOB recv_buffer = data_blob_null;
|
||||
struct ncacn_packet *pkt;
|
||||
NTSTATUS status;
|
||||
ssize_t data_left;
|
||||
ssize_t data_used;
|
||||
char *data;
|
||||
uint32_t to_send;
|
||||
size_t i;
|
||||
bool ok;
|
||||
@ -445,23 +442,20 @@ void named_pipe_packet_process(struct tevent_req *subreq)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data_left = recv_buffer.length;
|
||||
data = (char *)recv_buffer.data;
|
||||
|
||||
while (data_left) {
|
||||
|
||||
data_used = process_incoming_data(npc->p, data, data_left);
|
||||
if (data_used < 0) {
|
||||
DEBUG(3, ("Failed to process dceprc request!\n"));
|
||||
status = NT_STATUS_UNEXPECTED_IO_ERROR;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data_left -= data_used;
|
||||
data += data_used;
|
||||
/* dcerpc_read_ncacn_packet_recv() returns a full PDU */
|
||||
npc->p->in_data.pdu_needed_len = 0;
|
||||
npc->p->in_data.pdu = recv_buffer;
|
||||
if (dcerpc_get_endian_flag(&recv_buffer) & DCERPC_DREP_LE) {
|
||||
npc->p->endian = RPC_LITTLE_ENDIAN;
|
||||
} else {
|
||||
npc->p->endian = RPC_BIG_ENDIAN;
|
||||
}
|
||||
DEBUG(10, ("PDU is in %s Endian format!\n",
|
||||
npc->p->endian ? "Big" : "Little"));
|
||||
process_complete_pdu(npc->p, pkt);
|
||||
|
||||
/* Do not leak this buffer, npc is a long lived context */
|
||||
/* reset pipe state and free PDU */
|
||||
npc->p->in_data.pdu.length = 0;
|
||||
talloc_free(recv_buffer.data);
|
||||
talloc_free(pkt);
|
||||
|
||||
@ -1134,10 +1128,7 @@ static void dcerpc_ncacn_packet_process(struct tevent_req *subreq)
|
||||
struct _output_data *out = &ncacn_conn->p->out_data;
|
||||
DATA_BLOB recv_buffer = data_blob_null;
|
||||
struct ncacn_packet *pkt;
|
||||
ssize_t data_left;
|
||||
ssize_t data_used;
|
||||
uint32_t to_send;
|
||||
char *data;
|
||||
NTSTATUS status;
|
||||
bool ok;
|
||||
|
||||
@ -1153,22 +1144,20 @@ static void dcerpc_ncacn_packet_process(struct tevent_req *subreq)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data_left = recv_buffer.length;
|
||||
data = (char *) recv_buffer.data;
|
||||
|
||||
while (data_left) {
|
||||
data_used = process_incoming_data(ncacn_conn->p, data, data_left);
|
||||
if (data_used < 0) {
|
||||
DEBUG(3, ("Failed to process dcerpc request!\n"));
|
||||
status = NT_STATUS_UNEXPECTED_IO_ERROR;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data_left -= data_used;
|
||||
data += data_used;
|
||||
/* dcerpc_read_ncacn_packet_recv() returns a full PDU */
|
||||
ncacn_conn->p->in_data.pdu_needed_len = 0;
|
||||
ncacn_conn->p->in_data.pdu = recv_buffer;
|
||||
if (dcerpc_get_endian_flag(&recv_buffer) & DCERPC_DREP_LE) {
|
||||
ncacn_conn->p->endian = RPC_LITTLE_ENDIAN;
|
||||
} else {
|
||||
ncacn_conn->p->endian = RPC_BIG_ENDIAN;
|
||||
}
|
||||
DEBUG(10, ("PDU is in %s Endian format!\n",
|
||||
ncacn_conn->p->endian ? "Big" : "Little"));
|
||||
process_complete_pdu(ncacn_conn->p, pkt);
|
||||
|
||||
/* Do not leak this buffer */
|
||||
/* reset pipe state and free PDU */
|
||||
ncacn_conn->p->in_data.pdu.length = 0;
|
||||
talloc_free(recv_buffer.data);
|
||||
talloc_free(pkt);
|
||||
|
||||
|
@ -77,7 +77,7 @@ int make_server_pipes_struct(TALLOC_CTX *mem_ctx,
|
||||
int *perrno);
|
||||
|
||||
void set_incoming_fault(struct pipes_struct *p);
|
||||
void process_complete_pdu(struct pipes_struct *p);
|
||||
void process_complete_pdu(struct pipes_struct *p, struct ncacn_packet *pkt);
|
||||
int create_named_pipe_socket(const char *pipe_name);
|
||||
bool setup_named_pipe_socket(const char *pipe_name,
|
||||
struct tevent_context *ev_ctx,
|
||||
|
@ -1486,45 +1486,9 @@ static bool process_request_pdu(struct pipes_struct *p, struct ncacn_packet *pkt
|
||||
return True;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
Processes a finished PDU stored in p->in_data.pdu.
|
||||
****************************************************************************/
|
||||
|
||||
void process_complete_pdu(struct pipes_struct *p)
|
||||
void process_complete_pdu(struct pipes_struct *p, struct ncacn_packet *pkt)
|
||||
{
|
||||
struct ncacn_packet *pkt = NULL;
|
||||
NTSTATUS status;
|
||||
bool reply = False;
|
||||
|
||||
if(p->fault_state) {
|
||||
DEBUG(10,("RPC connection in fault state.\n"));
|
||||
goto done;
|
||||
}
|
||||
|
||||
pkt = talloc(p->mem_ctx, struct ncacn_packet);
|
||||
if (!pkt) {
|
||||
DEBUG(0, ("Out of memory!\n"));
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure we're using the corrent endianness for both the
|
||||
* RPC header flags and the raw data we will be reading from.
|
||||
*/
|
||||
if (dcerpc_get_endian_flag(&p->in_data.pdu) & DCERPC_DREP_LE) {
|
||||
p->endian = RPC_LITTLE_ENDIAN;
|
||||
} else {
|
||||
p->endian = RPC_BIG_ENDIAN;
|
||||
}
|
||||
DEBUG(10, ("PDU is in %s Endian format!\n", p->endian?"Big":"Little"));
|
||||
|
||||
status = dcerpc_pull_ncacn_packet(pkt, &p->in_data.pdu,
|
||||
pkt, p->endian);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
DEBUG(0, ("Failed to unmarshal rpc packet: %s!\n",
|
||||
nt_errstr(status)));
|
||||
goto done;
|
||||
}
|
||||
bool reply = false;
|
||||
|
||||
/* Store the call_id */
|
||||
p->call_id = pkt->call_id;
|
||||
@ -1644,21 +1608,11 @@ void process_complete_pdu(struct pipes_struct *p)
|
||||
break;
|
||||
}
|
||||
|
||||
done:
|
||||
if (!reply) {
|
||||
DEBUG(3,("DCE/RPC fault sent!"));
|
||||
set_incoming_fault(p);
|
||||
setup_fault_pdu(p, NT_STATUS(DCERPC_FAULT_OP_RNG_ERROR));
|
||||
TALLOC_FREE(pkt);
|
||||
} else {
|
||||
/*
|
||||
* Reset the lengths. We're ready for a new pdu.
|
||||
*/
|
||||
TALLOC_FREE(p->in_data.pdu.data);
|
||||
p->in_data.pdu_needed_len = 0;
|
||||
p->in_data.pdu.length = 0;
|
||||
}
|
||||
|
||||
TALLOC_FREE(pkt);
|
||||
/* pkt and p->in_data.pdu.data freed by caller */
|
||||
}
|
||||
|
||||
|
@ -35,372 +35,6 @@
|
||||
#undef DBGC_CLASS
|
||||
#define DBGC_CLASS DBGC_RPC_SRV
|
||||
|
||||
/****************************************************************************
|
||||
Ensures we have at least RPC_HEADER_LEN amount of data in the incoming buffer.
|
||||
****************************************************************************/
|
||||
|
||||
static ssize_t fill_rpc_header(struct pipes_struct *p, const char *data, size_t data_to_copy)
|
||||
{
|
||||
size_t len_needed_to_complete_hdr =
|
||||
MIN(data_to_copy, RPC_HEADER_LEN - p->in_data.pdu.length);
|
||||
|
||||
DEBUG(10, ("fill_rpc_header: data_to_copy = %u, "
|
||||
"len_needed_to_complete_hdr = %u, "
|
||||
"receive_len = %u\n",
|
||||
(unsigned int)data_to_copy,
|
||||
(unsigned int)len_needed_to_complete_hdr,
|
||||
(unsigned int)p->in_data.pdu.length ));
|
||||
|
||||
if (p->in_data.pdu.data == NULL) {
|
||||
p->in_data.pdu.data = talloc_array(p, uint8_t, RPC_HEADER_LEN);
|
||||
}
|
||||
if (p->in_data.pdu.data == NULL) {
|
||||
DEBUG(0, ("talloc failed\n"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy((char *)&p->in_data.pdu.data[p->in_data.pdu.length],
|
||||
data, len_needed_to_complete_hdr);
|
||||
p->in_data.pdu.length += len_needed_to_complete_hdr;
|
||||
|
||||
return (ssize_t)len_needed_to_complete_hdr;
|
||||
}
|
||||
|
||||
static bool get_pdu_size(struct pipes_struct *p)
|
||||
{
|
||||
uint16_t frag_len;
|
||||
/* the fill_rpc_header() call insures we copy only
|
||||
* RPC_HEADER_LEN bytes. If this doesn't match then
|
||||
* somethign is very wrong and we can only abort */
|
||||
if (p->in_data.pdu.length != RPC_HEADER_LEN) {
|
||||
DEBUG(0, ("Unexpected RPC Header size! "
|
||||
"got %d, expected %d)\n",
|
||||
(int)p->in_data.pdu.length,
|
||||
RPC_HEADER_LEN));
|
||||
set_incoming_fault(p);
|
||||
return false;
|
||||
}
|
||||
|
||||
frag_len = dcerpc_get_frag_length(&p->in_data.pdu);
|
||||
|
||||
/* verify it is a reasonable value */
|
||||
if ((frag_len < RPC_HEADER_LEN) ||
|
||||
(frag_len > RPC_MAX_PDU_FRAG_LEN)) {
|
||||
DEBUG(0, ("Unexpected RPC Fragment size! (%d)\n",
|
||||
frag_len));
|
||||
set_incoming_fault(p);
|
||||
return false;
|
||||
}
|
||||
|
||||
p->in_data.pdu_needed_len = frag_len - RPC_HEADER_LEN;
|
||||
|
||||
/* allocate the space needed to fill the pdu */
|
||||
p->in_data.pdu.data = talloc_realloc(p, p->in_data.pdu.data,
|
||||
uint8_t, frag_len);
|
||||
if (p->in_data.pdu.data == NULL) {
|
||||
DEBUG(0, ("talloc_realloc failed\n"));
|
||||
set_incoming_fault(p);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
Call this to free any talloc'ed memory. Do this after processing
|
||||
a complete incoming and outgoing request (multiple incoming/outgoing
|
||||
PDU's).
|
||||
****************************************************************************/
|
||||
|
||||
static void free_pipe_context(struct pipes_struct *p)
|
||||
{
|
||||
data_blob_free(&p->out_data.frag);
|
||||
data_blob_free(&p->out_data.rdata);
|
||||
data_blob_free(&p->in_data.data);
|
||||
|
||||
DEBUG(3, ("free_pipe_context: "
|
||||
"destroying talloc pool of size %lu\n",
|
||||
(unsigned long)talloc_total_size(p->mem_ctx)));
|
||||
talloc_free_children(p->mem_ctx);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
Accepts incoming data on an rpc pipe. Processes the data in pdu sized units.
|
||||
****************************************************************************/
|
||||
|
||||
ssize_t process_incoming_data(struct pipes_struct *p, const char *data, size_t n)
|
||||
{
|
||||
size_t data_to_copy = MIN(n, RPC_MAX_PDU_FRAG_LEN
|
||||
- p->in_data.pdu.length);
|
||||
|
||||
DEBUG(10, ("process_incoming_data: Start: pdu.length = %u, "
|
||||
"pdu_needed_len = %u, incoming data = %u\n",
|
||||
(unsigned int)p->in_data.pdu.length,
|
||||
(unsigned int)p->in_data.pdu_needed_len,
|
||||
(unsigned int)n ));
|
||||
|
||||
if(data_to_copy == 0) {
|
||||
/*
|
||||
* This is an error - data is being received and there is no
|
||||
* space in the PDU. Free the received data and go into the
|
||||
* fault state.
|
||||
*/
|
||||
DEBUG(0, ("process_incoming_data: "
|
||||
"No space in incoming pdu buffer. "
|
||||
"Current size = %u incoming data size = %u\n",
|
||||
(unsigned int)p->in_data.pdu.length,
|
||||
(unsigned int)n));
|
||||
set_incoming_fault(p);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we have no data already, wait until we get at least
|
||||
* a RPC_HEADER_LEN * number of bytes before we can do anything.
|
||||
*/
|
||||
|
||||
if ((p->in_data.pdu_needed_len == 0) &&
|
||||
(p->in_data.pdu.length < RPC_HEADER_LEN)) {
|
||||
/*
|
||||
* Always return here. If we have more data then the RPC_HEADER
|
||||
* will be processed the next time around the loop.
|
||||
*/
|
||||
return fill_rpc_header(p, data, data_to_copy);
|
||||
}
|
||||
|
||||
/*
|
||||
* At this point we know we have at least an RPC_HEADER_LEN amount of
|
||||
* data stored in p->in_data.pdu.
|
||||
*/
|
||||
|
||||
/*
|
||||
* If pdu_needed_len is zero this is a new pdu.
|
||||
* Check how much more data we need, then loop again.
|
||||
*/
|
||||
if (p->in_data.pdu_needed_len == 0) {
|
||||
|
||||
bool ok = get_pdu_size(p);
|
||||
if (!ok) {
|
||||
return -1;
|
||||
}
|
||||
if (p->in_data.pdu_needed_len > 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* If rret == 0 and pdu_needed_len == 0 here we have a PDU
|
||||
* that consists of an RPC_HEADER only. This is a
|
||||
* DCERPC_PKT_SHUTDOWN, DCERPC_PKT_CO_CANCEL or
|
||||
* DCERPC_PKT_ORPHANED pdu type.
|
||||
* Deal with this in process_complete_pdu(). */
|
||||
}
|
||||
|
||||
/*
|
||||
* Ok - at this point we have a valid RPC_HEADER.
|
||||
* Keep reading until we have a full pdu.
|
||||
*/
|
||||
|
||||
data_to_copy = MIN(data_to_copy, p->in_data.pdu_needed_len);
|
||||
|
||||
/*
|
||||
* Copy as much of the data as we need into the p->in_data.pdu buffer.
|
||||
* pdu_needed_len becomes zero when we have a complete pdu.
|
||||
*/
|
||||
|
||||
memcpy((char *)&p->in_data.pdu.data[p->in_data.pdu.length],
|
||||
data, data_to_copy);
|
||||
p->in_data.pdu.length += data_to_copy;
|
||||
p->in_data.pdu_needed_len -= data_to_copy;
|
||||
|
||||
/*
|
||||
* Do we have a complete PDU ?
|
||||
* (return the number of bytes handled in the call)
|
||||
*/
|
||||
|
||||
if(p->in_data.pdu_needed_len == 0) {
|
||||
process_complete_pdu(p);
|
||||
return data_to_copy;
|
||||
}
|
||||
|
||||
DEBUG(10, ("process_incoming_data: not a complete PDU yet. "
|
||||
"pdu.length = %u, pdu_needed_len = %u\n",
|
||||
(unsigned int)p->in_data.pdu.length,
|
||||
(unsigned int)p->in_data.pdu_needed_len));
|
||||
|
||||
return (ssize_t)data_to_copy;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
Accepts incoming data on an internal rpc pipe.
|
||||
****************************************************************************/
|
||||
|
||||
static ssize_t write_to_internal_pipe(struct pipes_struct *p, const char *data, size_t n)
|
||||
{
|
||||
size_t data_left = n;
|
||||
|
||||
while(data_left) {
|
||||
ssize_t data_used;
|
||||
|
||||
DEBUG(10, ("write_to_pipe: data_left = %u\n",
|
||||
(unsigned int)data_left));
|
||||
|
||||
data_used = process_incoming_data(p, data, data_left);
|
||||
|
||||
DEBUG(10, ("write_to_pipe: data_used = %d\n",
|
||||
(int)data_used));
|
||||
|
||||
if(data_used < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
data_left -= data_used;
|
||||
data += data_used;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
Replies to a request to read data from a pipe.
|
||||
|
||||
Headers are interspersed with the data at PDU intervals. By the time
|
||||
this function is called, the start of the data could possibly have been
|
||||
read by an SMBtrans (file_offset != 0).
|
||||
|
||||
Calling create_rpc_reply() here is a hack. The data should already
|
||||
have been prepared into arrays of headers + data stream sections.
|
||||
****************************************************************************/
|
||||
|
||||
static ssize_t read_from_internal_pipe(struct pipes_struct *p, char *data,
|
||||
size_t n, bool *is_data_outstanding)
|
||||
{
|
||||
uint32 pdu_remaining = 0;
|
||||
ssize_t data_returned = 0;
|
||||
|
||||
if (!p) {
|
||||
DEBUG(0,("read_from_pipe: pipe not open\n"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
DEBUG(6,(" name: %s len: %u\n",
|
||||
ndr_interface_name(&p->contexts->syntax.uuid,
|
||||
p->contexts->syntax.if_version),
|
||||
(unsigned int)n));
|
||||
|
||||
/*
|
||||
* We cannot return more than one PDU length per
|
||||
* read request.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This condition should result in the connection being closed.
|
||||
* Netapp filers seem to set it to 0xffff which results in domain
|
||||
* authentications failing. Just ignore it so things work.
|
||||
*/
|
||||
|
||||
if(n > RPC_MAX_PDU_FRAG_LEN) {
|
||||
DEBUG(5,("read_from_pipe: too large read (%u) requested on "
|
||||
"pipe %s. We can only service %d sized reads.\n",
|
||||
(unsigned int)n,
|
||||
ndr_interface_name(&p->contexts->syntax.uuid,
|
||||
p->contexts->syntax.if_version),
|
||||
RPC_MAX_PDU_FRAG_LEN ));
|
||||
n = RPC_MAX_PDU_FRAG_LEN;
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine if there is still data to send in the
|
||||
* pipe PDU buffer. Always send this first. Never
|
||||
* send more than is left in the current PDU. The
|
||||
* client should send a new read request for a new
|
||||
* PDU.
|
||||
*/
|
||||
|
||||
pdu_remaining = p->out_data.frag.length
|
||||
- p->out_data.current_pdu_sent;
|
||||
|
||||
if (pdu_remaining > 0) {
|
||||
data_returned = (ssize_t)MIN(n, pdu_remaining);
|
||||
|
||||
DEBUG(10,("read_from_pipe: %s: current_pdu_len = %u, "
|
||||
"current_pdu_sent = %u returning %d bytes.\n",
|
||||
ndr_interface_name(&p->contexts->syntax.uuid,
|
||||
p->contexts->syntax.if_version),
|
||||
(unsigned int)p->out_data.frag.length,
|
||||
(unsigned int)p->out_data.current_pdu_sent,
|
||||
(int)data_returned));
|
||||
|
||||
memcpy(data,
|
||||
p->out_data.frag.data
|
||||
+ p->out_data.current_pdu_sent,
|
||||
data_returned);
|
||||
|
||||
p->out_data.current_pdu_sent += (uint32)data_returned;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* At this point p->current_pdu_len == p->current_pdu_sent (which
|
||||
* may of course be zero if this is the first return fragment.
|
||||
*/
|
||||
|
||||
DEBUG(10,("read_from_pipe: %s: fault_state = %d : data_sent_length "
|
||||
"= %u, p->out_data.rdata.length = %u.\n",
|
||||
ndr_interface_name(&p->contexts->syntax.uuid,
|
||||
p->contexts->syntax.if_version),
|
||||
(int)p->fault_state,
|
||||
(unsigned int)p->out_data.data_sent_length,
|
||||
(unsigned int)p->out_data.rdata.length));
|
||||
|
||||
if (p->out_data.data_sent_length >= p->out_data.rdata.length) {
|
||||
/*
|
||||
* We have sent all possible data, return 0.
|
||||
*/
|
||||
data_returned = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* We need to create a new PDU from the data left in p->rdata.
|
||||
* Create the header/data/footers. This also sets up the fields
|
||||
* p->current_pdu_len, p->current_pdu_sent, p->data_sent_length
|
||||
* and stores the outgoing PDU in p->current_pdu.
|
||||
*/
|
||||
|
||||
if(!create_next_pdu(p)) {
|
||||
DEBUG(0,("read_from_pipe: %s: create_next_pdu failed.\n",
|
||||
ndr_interface_name(&p->contexts->syntax.uuid,
|
||||
p->contexts->syntax.if_version)));
|
||||
return -1;
|
||||
}
|
||||
|
||||
data_returned = MIN(n, p->out_data.frag.length);
|
||||
|
||||
memcpy(data, p->out_data.frag.data, (size_t)data_returned);
|
||||
p->out_data.current_pdu_sent += (uint32)data_returned;
|
||||
|
||||
out:
|
||||
(*is_data_outstanding) = p->out_data.frag.length > n;
|
||||
|
||||
if (p->out_data.current_pdu_sent == p->out_data.frag.length) {
|
||||
/* We've returned everything in the out_data.frag
|
||||
* so we're done with this pdu. Free it and reset
|
||||
* current_pdu_sent. */
|
||||
p->out_data.current_pdu_sent = 0;
|
||||
data_blob_free(&p->out_data.frag);
|
||||
|
||||
if (p->out_data.data_sent_length >= p->out_data.rdata.length) {
|
||||
/*
|
||||
* We're completely finished with both outgoing and
|
||||
* incoming data streams. It's safe to free all
|
||||
* temporary data from this request.
|
||||
*/
|
||||
free_pipe_context(p);
|
||||
}
|
||||
}
|
||||
|
||||
return data_returned;
|
||||
}
|
||||
|
||||
bool fsp_is_np(struct files_struct *fsp)
|
||||
{
|
||||
enum FAKE_FILE_TYPE type;
|
||||
|
@ -46,6 +46,4 @@ struct tevent_req *np_read_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
|
||||
NTSTATUS np_read_recv(struct tevent_req *req, ssize_t *nread,
|
||||
bool *is_data_outstanding);
|
||||
|
||||
ssize_t process_incoming_data(struct pipes_struct *p, const char *data, size_t n);
|
||||
|
||||
#endif /* _RPC_SERVER_SRV_PIPE_HND_H_ */
|
||||
|
Loading…
Reference in New Issue
Block a user