summaryrefslogtreecommitdiff
path: root/source4/librpc/rpc/dcerpc.c
diff options
context:
space:
mode:
Diffstat (limited to 'source4/librpc/rpc/dcerpc.c')
-rw-r--r--source4/librpc/rpc/dcerpc.c492
1 files changed, 354 insertions, 138 deletions
diff --git a/source4/librpc/rpc/dcerpc.c b/source4/librpc/rpc/dcerpc.c
index b605b4d110..1a8fe7373a 100644
--- a/source4/librpc/rpc/dcerpc.c
+++ b/source4/librpc/rpc/dcerpc.c
@@ -28,18 +28,12 @@ struct dcerpc_pipe *dcerpc_pipe_init(void)
{
struct dcerpc_pipe *p;
- TALLOC_CTX *mem_ctx = talloc_init("dcerpc_tree");
- if (mem_ctx == NULL)
- return NULL;
-
- p = talloc(mem_ctx, sizeof(*p));
+ p = talloc_p(NULL, struct dcerpc_pipe);
if (!p) {
- talloc_destroy(mem_ctx);
return NULL;
}
p->reference_count = 0;
- p->mem_ctx = mem_ctx;
p->call_id = 1;
p->security_state.auth_info = NULL;
p->security_state.generic_state = NULL;
@@ -48,10 +42,23 @@ struct dcerpc_pipe *dcerpc_pipe_init(void)
p->srv_max_xmit_frag = 0;
p->srv_max_recv_frag = 0;
p->last_fault_code = 0;
+ p->pending = NULL;
return p;
}
+/*
+ choose the next call id to use
+*/
+static uint32_t next_call_id(struct dcerpc_pipe *p)
+{
+ p->call_id++;
+ if (p->call_id == 0) {
+ p->call_id++;
+ }
+ return p->call_id;
+}
+
/* close down a dcerpc over SMB pipe */
void dcerpc_pipe_close(struct dcerpc_pipe *p)
{
@@ -62,7 +69,7 @@ void dcerpc_pipe_close(struct dcerpc_pipe *p)
gensec_end(&p->security_state.generic_state);
}
p->transport.shutdown_pipe(p);
- talloc_destroy(p->mem_ctx);
+ talloc_free(p);
}
}
@@ -320,6 +327,67 @@ static void init_dcerpc_hdr(struct dcerpc_pipe *p, struct dcerpc_packet *pkt)
pkt->drep[3] = 0;
}
+/*
+ hold the state of pending full requests
+*/
+struct full_request_state {
+ DATA_BLOB *reply_blob;
+ NTSTATUS status;
+};
+
+/*
+ receive a reply to a full request
+ */
+static void full_request_recv(struct dcerpc_pipe *p, DATA_BLOB *blob,
+ NTSTATUS status)
+{
+ struct full_request_state *state = p->full_request_private;
+
+ if (!NT_STATUS_IS_OK(status)) {
+ state->status = status;
+ return;
+ }
+ state->reply_blob[0] = data_blob_talloc(state, blob->data, blob->length);
+ state->reply_blob = NULL;
+}
+
+/*
+ perform a synchronous request - used for the bind code
+ this cannot be mixed with normal async requests
+*/
+static NTSTATUS full_request(struct dcerpc_pipe *p,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *request_blob,
+ DATA_BLOB *reply_blob)
+{
+ struct full_request_state *state = talloc_p(mem_ctx, struct full_request_state);
+ NTSTATUS status;
+
+ if (state == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ state->reply_blob = reply_blob;
+ state->status = NT_STATUS_OK;
+
+ p->transport.recv_data = full_request_recv;
+ p->full_request_private = state;
+
+ status = p->transport.send_request(p, request_blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ p->transport.send_read(p);
+
+ while (NT_STATUS_IS_OK(state->status) && state->reply_blob) {
+ struct event_context *ctx = p->transport.event_context(p);
+ event_loop_once(ctx);
+ }
+
+ return state->status;
+}
+
/*
perform a bind using the given syntax
@@ -367,7 +435,7 @@ NTSTATUS dcerpc_bind(struct dcerpc_pipe *p,
}
/* send it on its way */
- status = p->transport.full_request(p, mem_ctx, &blob, &blob);
+ status = full_request(p, mem_ctx, &blob, &blob);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
@@ -441,7 +509,7 @@ NTSTATUS dcerpc_alter(struct dcerpc_pipe *p,
}
/* send it on its way */
- status = p->transport.full_request(p, mem_ctx, &blob, &blob);
+ status = full_request(p, mem_ctx, &blob, &blob);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
@@ -483,7 +551,7 @@ NTSTATUS dcerpc_auth3(struct dcerpc_pipe *p,
pkt.ptype = DCERPC_PKT_AUTH3;
pkt.pfc_flags = DCERPC_PFC_FLAG_FIRST | DCERPC_PFC_FLAG_LAST;
- pkt.call_id = p->call_id++;
+ pkt.call_id = next_call_id(p);
pkt.auth_length = 0;
pkt.u.auth._pad = 0;
pkt.u.auth.auth_info = data_blob(NULL, 0);
@@ -495,7 +563,7 @@ NTSTATUS dcerpc_auth3(struct dcerpc_pipe *p,
}
/* send it on its way */
- status = p->transport.initial_request(p, mem_ctx, &blob);
+ status = p->transport.send_request(p, &blob);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
@@ -530,33 +598,141 @@ NTSTATUS dcerpc_bind_byuuid(struct dcerpc_pipe *p,
}
/*
- perform a full request/response pair on a dcerpc pipe
+ process a fragment received from the transport layer during a
+ request
*/
-NTSTATUS dcerpc_request(struct dcerpc_pipe *p,
- uint16_t opnum,
- TALLOC_CTX *mem_ctx,
- DATA_BLOB *stub_data_in,
- DATA_BLOB *stub_data_out)
+static void dcerpc_request_recv_data(struct dcerpc_pipe *p,
+ DATA_BLOB *data,
+ NTSTATUS status)
{
+ struct dcerpc_packet pkt;
+ struct rpc_request *req;
+ uint_t length;
+ if (!NT_STATUS_IS_OK(status)) {
+ /* all pending requests get the error */
+ while (p->pending) {
+ req = p->pending;
+ req->state = RPC_REQUEST_DONE;
+ req->status = status;
+ DLIST_REMOVE(p->pending, req);
+ }
+ return;
+ }
+
+ pkt.call_id = 0;
+
+ status = dcerpc_pull_request_sign(p, data, (TALLOC_CTX *)data->data, &pkt);
+
+ /* find the matching request. Notice we match before we check
+ the status. this is ok as a pending call_id can never be
+ zero */
+ for (req=p->pending;req;req=req->next) {
+ if (pkt.call_id == req->call_id) break;
+ }
+
+ if (req == NULL) {
+ DEBUG(2,("dcerpc_request: unmatched call_id in response packet\n"));
+ return;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ req->status = status;
+ req->state = RPC_REQUEST_DONE;
+ DLIST_REMOVE(p->pending, req);
+ return;
+ }
+
+ if (pkt.ptype == DCERPC_PKT_FAULT) {
+ DEBUG(5,("rpc fault 0x%x\n", pkt.u.fault.status));
+ req->fault_code = pkt.u.fault.status;
+ req->status = NT_STATUS_NET_WRITE_FAULT;
+ req->state = RPC_REQUEST_DONE;
+ DLIST_REMOVE(p->pending, req);
+ return;
+ }
+
+ if (pkt.ptype != DCERPC_PKT_RESPONSE) {
+ DEBUG(2,("Unexpected packet type %d in dcerpc response\n",
+ (int)pkt.ptype));
+ req->fault_code = DCERPC_FAULT_OTHER;
+ req->status = NT_STATUS_NET_WRITE_FAULT;
+ req->state = RPC_REQUEST_DONE;
+ DLIST_REMOVE(p->pending, req);
+ return;
+ }
+
+ length = pkt.u.response.stub_and_verifier.length;
+
+ if (length > 0) {
+ req->payload.data = talloc_realloc(req->payload.data,
+ req->payload.length + length);
+ if (!req->payload.data) {
+ req->status = NT_STATUS_NO_MEMORY;
+ req->state = RPC_REQUEST_DONE;
+ DLIST_REMOVE(p->pending, req);
+ return;
+ }
+ memcpy(req->payload.data+req->payload.length,
+ pkt.u.response.stub_and_verifier.data, length);
+ req->payload.length += length;
+ }
+
+ if (!(pkt.pfc_flags & DCERPC_PFC_FLAG_LAST)) {
+ p->transport.send_read(p);
+ return;
+ }
+
+ /* we've got the full payload */
+ req->state = RPC_REQUEST_DONE;
+ DLIST_REMOVE(p->pending, req);
+
+ if (!(pkt.drep[0] & DCERPC_DREP_LE)) {
+ req->flags |= DCERPC_PULL_BIGENDIAN;
+ } else {
+ req->flags &= ~DCERPC_PULL_BIGENDIAN;
+ }
+}
+
+
+/*
+ perform a full request/response pair on a dcerpc pipe
+*/
+struct rpc_request *dcerpc_request_send(struct dcerpc_pipe *p,
+ uint16_t opnum,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *stub_data)
+{
+ struct rpc_request *req;
struct dcerpc_packet pkt;
- NTSTATUS status;
- DATA_BLOB blob, payload;
+ DATA_BLOB blob;
uint32_t remaining, chunk_size;
- /* allow the application to tell when a fault has happened */
- p->last_fault_code = 0;
+ p->transport.recv_data = dcerpc_request_recv_data;
+
+ req = talloc_p(mem_ctx, struct rpc_request);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ req->p = p;
+ req->call_id = next_call_id(p);
+ req->status = NT_STATUS_OK;
+ req->state = RPC_REQUEST_PENDING;
+ req->payload = data_blob(NULL, 0);
+ req->flags = 0;
+ req->fault_code = 0;
init_dcerpc_hdr(p, &pkt);
- remaining = stub_data_in->length;
+ remaining = stub_data->length;
/* we can write a full max_recv_frag size, minus the dcerpc
request header size */
chunk_size = p->srv_max_recv_frag - (DCERPC_MAX_SIGN_SIZE+DCERPC_REQUEST_LENGTH);
pkt.ptype = DCERPC_PKT_REQUEST;
- pkt.call_id = p->call_id++;
+ pkt.call_id = req->call_id;
pkt.auth_length = 0;
pkt.u.request.alloc_hint = remaining;
pkt.u.request.context_id = 0;
@@ -565,24 +741,26 @@ NTSTATUS dcerpc_request(struct dcerpc_pipe *p,
/* we send a series of pdus without waiting for a reply until
the last pdu */
while (remaining > chunk_size) {
- if (remaining == stub_data_in->length) {
+ if (remaining == stub_data->length) {
pkt.pfc_flags = DCERPC_PFC_FLAG_FIRST;
} else {
pkt.pfc_flags = 0;
}
- pkt.u.request.stub_and_verifier.data = stub_data_in->data +
- (stub_data_in->length - remaining);
+ pkt.u.request.stub_and_verifier.data = stub_data->data +
+ (stub_data->length - remaining);
pkt.u.request.stub_and_verifier.length = chunk_size;
- status = dcerpc_push_request_sign(p, &blob, mem_ctx, &pkt);
- if (!NT_STATUS_IS_OK(status)) {
- return status;
+ req->status = dcerpc_push_request_sign(p, &blob, mem_ctx, &pkt);
+ if (!NT_STATUS_IS_OK(req->status)) {
+ req->state = RPC_REQUEST_DONE;
+ return req;
}
- status = p->transport.initial_request(p, mem_ctx, &blob);
- if (!NT_STATUS_IS_OK(status)) {
- return status;
+ req->status = p->transport.send_request(p, &blob);
+ if (!NT_STATUS_IS_OK(req->status)) {
+ req->state = RPC_REQUEST_DONE;
+ return req;
}
remaining -= chunk_size;
@@ -590,102 +768,88 @@ NTSTATUS dcerpc_request(struct dcerpc_pipe *p,
/* now we send a pdu with LAST_FRAG sent and get the first
part of the reply */
- if (remaining == stub_data_in->length) {
+ if (remaining == stub_data->length) {
pkt.pfc_flags = DCERPC_PFC_FLAG_FIRST | DCERPC_PFC_FLAG_LAST;
} else {
pkt.pfc_flags = DCERPC_PFC_FLAG_LAST;
}
- pkt.u.request.stub_and_verifier.data = stub_data_in->data +
- (stub_data_in->length - remaining);
+ pkt.u.request.stub_and_verifier.data = stub_data->data +
+ (stub_data->length - remaining);
pkt.u.request.stub_and_verifier.length = remaining;
- status = dcerpc_push_request_sign(p, &blob, mem_ctx, &pkt);
- if (!NT_STATUS_IS_OK(status)) {
- return status;
+ req->status = dcerpc_push_request_sign(p, &blob, mem_ctx, &pkt);
+ if (!NT_STATUS_IS_OK(req->status)) {
+ req->state = RPC_REQUEST_DONE;
+ return req;
}
- /* send the pdu and get the initial response pdu */
- status = p->transport.full_request(p, mem_ctx, &blob, &blob);
- if (!NT_STATUS_IS_OK(status)) {
- return status;
- }
+ /* send the pdu */
+ req->status = p->transport.send_request(p, &blob);
- status = dcerpc_pull_request_sign(p, &blob, mem_ctx, &pkt);
- if (!NT_STATUS_IS_OK(status)) {
- return status;
+ if (!NT_STATUS_IS_OK(req->status)) {
+ req->state = RPC_REQUEST_DONE;
}
- if (pkt.ptype == DCERPC_PKT_FAULT) {
- DEBUG(5,("rpc fault 0x%x\n", pkt.u.fault.status));
- p->last_fault_code = pkt.u.fault.status;
- return NT_STATUS_NET_WRITE_FAULT;
- }
+ DLIST_ADD(p->pending, req);
- if (pkt.ptype != DCERPC_PKT_RESPONSE) {
- return NT_STATUS_UNSUCCESSFUL;
- }
+ p->transport.send_read(p);
- if (!(pkt.pfc_flags & DCERPC_PFC_FLAG_FIRST)) {
- /* something is badly wrong! */
- return NT_STATUS_UNSUCCESSFUL;
- }
-
- payload = pkt.u.response.stub_and_verifier;
-
- /* continue receiving fragments */
- while (!(pkt.pfc_flags & DCERPC_PFC_FLAG_LAST)) {
- uint32_t length;
-
- status = p->transport.secondary_request(p, mem_ctx, &blob);
- if (!NT_STATUS_IS_OK(status)) {
- return status;
- }
-
- status = dcerpc_pull_request_sign(p, &blob, mem_ctx, &pkt);
- if (!NT_STATUS_IS_OK(status)) {
- return status;
- }
-
- if (pkt.pfc_flags & DCERPC_PFC_FLAG_FIRST) {
- /* start of another packet!? */
- return NT_STATUS_UNSUCCESSFUL;
- }
-
- if (pkt.ptype == DCERPC_PKT_FAULT) {
- p->last_fault_code = pkt.u.fault.status;
- return NT_STATUS_NET_WRITE_FAULT;
- }
+ return req;
+}
- if (pkt.ptype != DCERPC_PKT_RESPONSE) {
- return NT_STATUS_UNSUCCESSFUL;
- }
+/*
+ return the event context for a dcerpc pipe
+ used by callers who wish to operate asynchronously
+*/
+struct event_context *dcerpc_event_context(struct dcerpc_pipe *p)
+{
+ return p->transport.event_context(p);
+}
- length = pkt.u.response.stub_and_verifier.length;
- payload.data = talloc_realloc(payload.data,
- payload.length + length);
- if (!payload.data) {
- return NT_STATUS_NO_MEMORY;
- }
- memcpy(payload.data + payload.length,
- pkt.u.response.stub_and_verifier.data,
- length);
+/*
+ perform a full request/response pair on a dcerpc pipe
+*/
+NTSTATUS dcerpc_request_recv(struct rpc_request *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *stub_data)
+{
+ NTSTATUS status;
- payload.length += length;
+ while (req->state == RPC_REQUEST_PENDING) {
+ struct event_context *ctx = dcerpc_event_context(req->p);
+ event_loop_once(ctx);
}
-
- if (stub_data_out) {
- *stub_data_out = payload;
+ *stub_data = req->payload;
+ status = req->status;
+ if (stub_data->data) {
+ stub_data->data = talloc_steal(mem_ctx, stub_data->data);
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NET_WRITE_FAULT)) {
+ req->p->last_fault_code = req->fault_code;
}
+ talloc_free(req);
+ return status;
+}
- if (!(pkt.drep[0] & DCERPC_DREP_LE)) {
- p->flags |= DCERPC_PULL_BIGENDIAN;
- } else {
- p->flags &= ~DCERPC_PULL_BIGENDIAN;
+/*
+ perform a full request/response pair on a dcerpc pipe
+*/
+NTSTATUS dcerpc_request(struct dcerpc_pipe *p,
+ uint16_t opnum,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *stub_data_in,
+ DATA_BLOB *stub_data_out)
+{
+ struct rpc_request *req;
+
+ req = dcerpc_request_send(p, opnum, mem_ctx, stub_data_in);
+ if (req == NULL) {
+ return NT_STATUS_NO_MEMORY;
}
- return status;
+ return dcerpc_request_recv(req, mem_ctx, stub_data_out);
}
@@ -830,30 +994,29 @@ static NTSTATUS dcerpc_ndr_validate_out(TALLOC_CTX *mem_ctx,
return NT_STATUS_OK;
}
+
/*
- a useful helper function for synchronous rpc requests
+ send a rpc request with a given set of ndr helper functions
- this can be used when you have ndr push/pull functions in the
- standard format
+ call dcerpc_ndr_request_recv() to receive the answer
*/
-NTSTATUS dcerpc_ndr_request(struct dcerpc_pipe *p,
- uint32_t opnum,
- TALLOC_CTX *mem_ctx,
- NTSTATUS (*ndr_push)(struct ndr_push *, int, void *),
- NTSTATUS (*ndr_pull)(struct ndr_pull *, int, void *),
- void *struct_ptr,
- size_t struct_size)
+struct rpc_request *dcerpc_ndr_request_send(struct dcerpc_pipe *p,
+ uint32_t opnum,
+ TALLOC_CTX *mem_ctx,
+ NTSTATUS (*ndr_push)(struct ndr_push *, int, void *),
+ NTSTATUS (*ndr_pull)(struct ndr_pull *, int, void *),
+ void *struct_ptr,
+ size_t struct_size)
{
struct ndr_push *push;
- struct ndr_pull *pull;
NTSTATUS status;
- DATA_BLOB request, response;
+ DATA_BLOB request;
+ struct rpc_request *req;
/* setup for a ndr_push_* call */
push = ndr_push_init();
if (!push) {
- talloc_destroy(mem_ctx);
- return NT_STATUS_NO_MEMORY;
+ return NULL;
}
if (p->flags & DCERPC_PUSH_BIGENDIAN) {
@@ -863,7 +1026,10 @@ NTSTATUS dcerpc_ndr_request(struct dcerpc_pipe *p,
/* push the structure into a blob */
status = ndr_push(push, NDR_IN, struct_ptr);
if (!NT_STATUS_IS_OK(status)) {
- goto failed;
+ DEBUG(2,("Unable to ndr_push structure in dcerpc_ndr_request_send - %s\n",
+ nt_errstr(status)));
+ ndr_push_free(push);
+ return NULL;
}
/* retrieve the blob */
@@ -873,7 +1039,10 @@ NTSTATUS dcerpc_ndr_request(struct dcerpc_pipe *p,
status = dcerpc_ndr_validate_in(mem_ctx, request, struct_size,
ndr_push, ndr_pull);
if (!NT_STATUS_IS_OK(status)) {
- goto failed;
+ DEBUG(2,("Validation failed in dcerpc_ndr_request_send - %s\n",
+ nt_errstr(status)));
+ ndr_push_free(push);
+ return NULL;
}
}
@@ -881,18 +1050,45 @@ NTSTATUS dcerpc_ndr_request(struct dcerpc_pipe *p,
dump_data(10, request.data, request.length);
/* make the actual dcerpc request */
- status = dcerpc_request(p, opnum, mem_ctx, &request, &response);
+ req = dcerpc_request_send(p, opnum, mem_ctx, &request);
+
+ if (req != NULL) {
+ req->ndr.ndr_push = ndr_push;
+ req->ndr.ndr_pull = ndr_pull;
+ req->ndr.struct_ptr = struct_ptr;
+ req->ndr.struct_size = struct_size;
+ req->ndr.mem_ctx = mem_ctx;
+ }
+
+ ndr_push_free(push);
+
+ return req;
+}
+
+/*
+ receive the answer from a dcerpc_ndr_request_send()
+*/
+NTSTATUS dcerpc_ndr_request_recv(struct rpc_request *req)
+{
+ struct dcerpc_pipe *p = req->p;
+ NTSTATUS status;
+ DATA_BLOB response;
+ struct ndr_pull *pull;
+ struct rpc_request_ndr ndr = req->ndr;
+ uint_t flags = req->flags;
+
+ status = dcerpc_request_recv(req, ndr.mem_ctx, &response);
if (!NT_STATUS_IS_OK(status)) {
- goto failed;
+ return status;
}
/* prepare for ndr_pull_* */
- pull = ndr_pull_init_blob(&response, mem_ctx);
+ pull = ndr_pull_init_blob(&response, ndr.mem_ctx);
if (!pull) {
- goto failed;
+ return NT_STATUS_NO_MEMORY;
}
- if (p->flags & DCERPC_PULL_BIGENDIAN) {
+ if (flags & DCERPC_PULL_BIGENDIAN) {
pull->flags |= LIBNDR_FLAG_BIGENDIAN;
}
@@ -900,19 +1096,16 @@ NTSTATUS dcerpc_ndr_request(struct dcerpc_pipe *p,
dump_data(10, pull->data, pull->data_size);
/* pull the structure from the blob */
- status = ndr_pull(pull, NDR_OUT, struct_ptr);
+ status = ndr.ndr_pull(pull, NDR_OUT, ndr.struct_ptr);
if (!NT_STATUS_IS_OK(status)) {
- goto failed;
+ return status;
}
- /* possibly check the packet signature */
-
-
if (p->flags & DCERPC_DEBUG_VALIDATE_OUT) {
- status = dcerpc_ndr_validate_out(mem_ctx, struct_ptr, struct_size,
- ndr_push, ndr_pull);
+ status = dcerpc_ndr_validate_out(ndr.mem_ctx, ndr.struct_ptr, ndr.struct_size,
+ ndr.ndr_push, ndr.ndr_pull);
if (!NT_STATUS_IS_OK(status)) {
- goto failed;
+ return status;
}
}
@@ -926,9 +1119,32 @@ NTSTATUS dcerpc_ndr_request(struct dcerpc_pipe *p,
those versions then we need to ignore this error */
}
-failed:
- ndr_push_free(push);
- return status;
+ return NT_STATUS_OK;
+}
+
+
+/*
+ a useful helper function for synchronous rpc requests
+
+ this can be used when you have ndr push/pull functions in the
+ standard format
+*/
+NTSTATUS dcerpc_ndr_request(struct dcerpc_pipe *p,
+ uint32_t opnum,
+ TALLOC_CTX *mem_ctx,
+ NTSTATUS (*ndr_push)(struct ndr_push *, int, void *),
+ NTSTATUS (*ndr_pull)(struct ndr_pull *, int, void *),
+ void *struct_ptr,
+ size_t struct_size)
+{
+ struct rpc_request *req;
+
+ req = dcerpc_ndr_request_send(p, opnum, mem_ctx, ndr_push, ndr_pull, struct_ptr, struct_size);
+ if (req == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return dcerpc_ndr_request_recv(req);
}