diff options
author | Andrew Tridgell <tridge@samba.org> | 2004-08-30 03:10:43 +0000 |
---|---|---|
committer | Gerald (Jerry) Carter <jerry@samba.org> | 2007-10-10 12:58:24 -0500 |
commit | e7f36ff1a5ec909573ef398d215608e7c9aa71fe (patch) | |
tree | 8874e1dbc422a02631e48c772dc8749f255819aa /source4/librpc/rpc/dcerpc.c | |
parent | 18bbab726884725ccf2f3264223866194855f320 (diff) | |
download | samba-e7f36ff1a5ec909573ef398d215608e7c9aa71fe.tar.gz samba-e7f36ff1a5ec909573ef398d215608e7c9aa71fe.tar.bz2 samba-e7f36ff1a5ec909573ef398d215608e7c9aa71fe.zip |
r2100: rework the dcerpc client side library so that it is async. We now
generate a separate *_send() async function for every RPC call, and
there is a single dcerpc_ndr_request_recv() call that processes the
receive side of any rpc call. The caller can use
dcerpc_event_context() to get a pointer to the event context for the
pipe so that events can be waited for asynchronously.
The only part that remains synchronous is the initial bind
calls. These could also be made async if necessary, although I suspect
most applications won't need them to be.
(This used to be commit f5d004d8eb8c76c03342cace1976b27266cfa1f0)
Diffstat (limited to 'source4/librpc/rpc/dcerpc.c')
-rw-r--r-- | source4/librpc/rpc/dcerpc.c | 492 |
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); } |