From 16309de71d6c8de96e869aeaab0b879185991d87 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Fri, 12 Dec 2003 03:59:09 +0000 Subject: * the RPC-ECHO pipe now works in smbd, as long as the data sizes don't cause fragmented pdus (I'll add fragments shortly) * change data_blob_talloc() to not zero memory when the 2nd argument is NULL. The zeroing just masks bugs, and can't even allow a DOS attack * modified pidl to ensure that [ref] arguments to the out side of functions are allocated when parsing the in side. This allows rpc backends to assume that [ref] variables are all setup. Doesn't work correctly for [ref] arrays yet * changed DLIST_ADD_END() to take the type instead of a tmp variable. This means you don't need to declare a silly tmp variable in the caller (This used to be commit 46e0a358198eeb9af1907ee2a29025d3ab23b6d1) --- source4/rpc_server/dcerpc_server.c | 308 ++++++++++++++++++++++++++++++++++++- source4/rpc_server/dcerpc_server.h | 31 +++- source4/rpc_server/rpc_echo.c | 79 +++++++++- 3 files changed, 408 insertions(+), 10 deletions(-) (limited to 'source4/rpc_server') diff --git a/source4/rpc_server/dcerpc_server.c b/source4/rpc_server/dcerpc_server.c index e1d6da2292..f5e7ff858e 100644 --- a/source4/rpc_server/dcerpc_server.c +++ b/source4/rpc_server/dcerpc_server.c @@ -30,7 +30,7 @@ static const struct dcesrv_endpoint_ops *find_endpoint(struct server_context *sm { struct dce_endpoint *ep; for (ep=smb->dcesrv.endpoint_list; ep; ep=ep->next) { - if (ep->endpoint_ops->query(endpoint)) { + if (ep->endpoint_ops->query_endpoint(endpoint)) { return ep->endpoint_ops; } } @@ -86,6 +86,7 @@ NTSTATUS dcesrv_endpoint_connect(struct server_context *smb, (*p)->endpoint = *endpoint; (*p)->ops = ops; (*p)->private = NULL; + (*p)->call_list = NULL; /* make sure the endpoint server likes the connection */ status = ops->connect(*p); @@ -107,23 +108,318 @@ void dcesrv_endpoint_disconnect(struct dcesrv_state *p) talloc_destroy(p->mem_ctx); } +/* + return a dcerpc fault +*/ +static NTSTATUS dcesrv_fault(struct dcesrv_call_state *call, uint32 fault_code) +{ + struct ndr_push *push; + struct dcerpc_packet pkt; + NTSTATUS status; + + /* setup a bind_ack */ + pkt.rpc_vers = 5; + pkt.rpc_vers_minor = 0; + pkt.drep[0] = 0x10; /* Little endian */ + pkt.drep[1] = 0; + pkt.drep[2] = 0; + pkt.drep[3] = 0; + pkt.auth_length = 0; + pkt.call_id = call->pkt.call_id; + pkt.ptype = DCERPC_PKT_FAULT; + pkt.pfc_flags = DCERPC_PFC_FLAG_FIRST | DCERPC_PFC_FLAG_LAST; + pkt.u.fault.alloc_hint = 0; + pkt.u.fault.context_id = 0; + pkt.u.fault.cancel_count = 0; + pkt.u.fault.status = fault_code; + + /* now form the NDR for the bind_ack */ + push = ndr_push_init_ctx(call->mem_ctx); + if (!push) { + return NT_STATUS_NO_MEMORY; + } + + status = ndr_push_dcerpc_packet(push, NDR_SCALARS|NDR_BUFFERS, &pkt); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + call->data = ndr_push_blob(push); + SSVAL(call->data.data, DCERPC_FRAG_LEN_OFFSET, call->data.length); + + return NT_STATUS_OK; +} + + +/* + handle a bind request +*/ +static NTSTATUS dcesrv_bind(struct dcesrv_call_state *call) +{ + const char *uuid, *transfer_syntax; + uint32 if_version, transfer_syntax_version; + struct dcerpc_packet pkt; + struct ndr_push *push; + NTSTATUS status; + + if (call->pkt.u.bind.num_contexts != 1 || + call->pkt.u.bind.ctx_list[0].num_transfer_syntaxes < 1) { + return dcesrv_fault(call, DCERPC_FAULT_TODO); + } + + if_version = call->pkt.u.bind.ctx_list[0].abstract_syntax.major_version; + uuid = GUID_string(call->mem_ctx, &call->pkt.u.bind.ctx_list[0].abstract_syntax.uuid); + if (!uuid) { + return dcesrv_fault(call, DCERPC_FAULT_TODO); + } + + transfer_syntax_version = call->pkt.u.bind.ctx_list[0].transfer_syntaxes[0].major_version; + transfer_syntax = GUID_string(call->mem_ctx, + &call->pkt.u.bind.ctx_list[0].transfer_syntaxes[0].uuid); + if (!transfer_syntax || + strcasecmp(NDR_GUID, transfer_syntax) != 0 || + NDR_GUID_VERSION != transfer_syntax_version) { + /* we only do NDR encoded dcerpc */ + return dcesrv_fault(call, DCERPC_FAULT_TODO); + } + + if (!call->dce->ops->set_interface(call->dce, uuid, if_version)) { + DEBUG(2,("Request for unknown dcerpc interface %s/%d\n", uuid, if_version)); + /* we don't know about that interface */ + return dcesrv_fault(call, DCERPC_FAULT_TODO); + } + + /* setup a bind_ack */ + pkt.rpc_vers = 5; + pkt.rpc_vers_minor = 0; + pkt.drep[0] = 0x10; /* Little endian */ + pkt.drep[1] = 0; + pkt.drep[2] = 0; + pkt.drep[3] = 0; + pkt.auth_length = 0; + pkt.call_id = call->pkt.call_id; + pkt.ptype = DCERPC_PKT_BIND_ACK; + pkt.pfc_flags = DCERPC_PFC_FLAG_FIRST | DCERPC_PFC_FLAG_LAST; + pkt.u.bind_ack.max_xmit_frag = 0x2000; + pkt.u.bind_ack.max_recv_frag = 0x2000; + pkt.u.bind_ack.assoc_group_id = call->pkt.u.bind.assoc_group_id; + pkt.u.bind_ack.secondary_address = talloc_asprintf(call->mem_ctx, "\\PIPE\\%s", + call->dce->ndr->name); + pkt.u.bind_ack.num_results = 1; + pkt.u.bind_ack.ctx_list = talloc(call->mem_ctx, sizeof(struct dcerpc_ack_ctx)); + if (!pkt.u.bind_ack.ctx_list) { + return NT_STATUS_NO_MEMORY; + } + pkt.u.bind_ack.ctx_list[0].result = 0; + pkt.u.bind_ack.ctx_list[0].reason = 0; + GUID_from_string(uuid, &pkt.u.bind_ack.ctx_list[0].syntax.uuid); + pkt.u.bind_ack.ctx_list[0].syntax.major_version = if_version; + pkt.u.bind_ack.ctx_list[0].syntax.minor_version = 0; + pkt.u.bind_ack.auth_info = data_blob(NULL, 0); + + /* now form the NDR for the bind_ack */ + push = ndr_push_init_ctx(call->mem_ctx); + if (!push) { + return NT_STATUS_NO_MEMORY; + } + + status = ndr_push_dcerpc_packet(push, NDR_SCALARS|NDR_BUFFERS, &pkt); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + call->data = ndr_push_blob(push); + SSVAL(call->data.data, DCERPC_FRAG_LEN_OFFSET, call->data.length); + + return NT_STATUS_OK; +} + + +/* + handle a dcerpc request packet +*/ +static NTSTATUS dcesrv_request(struct dcesrv_call_state *call) +{ + struct ndr_pull *pull; + struct ndr_push *push; + uint16 opnum; + void *r; + NTSTATUS status; + DATA_BLOB stub; + struct dcerpc_packet pkt; + + if (call->pkt.pfc_flags != (DCERPC_PFC_FLAG_FIRST | DCERPC_PFC_FLAG_LAST)) { + /* we don't do fragments in the server yet */ + return dcesrv_fault(call, DCERPC_FAULT_TODO); + } + + opnum = call->pkt.u.request.opnum; + + if (opnum >= call->dce->ndr->num_calls) { + return dcesrv_fault(call, DCERPC_FAULT_OP_RNG_ERROR); + } + + pull = ndr_pull_init_blob(&call->pkt.u.request.stub_and_verifier, call->mem_ctx); + if (!pull) { + return NT_STATUS_NO_MEMORY; + } + + r = talloc(call->mem_ctx, call->dce->ndr->calls[opnum].struct_size); + if (!r) { + return NT_STATUS_NO_MEMORY; + } + + /* unravel the NDR for the packet */ + status = call->dce->ndr->calls[opnum].ndr_pull(pull, NDR_IN, r); + if (!NT_STATUS_IS_OK(status)) { + return dcesrv_fault(call, DCERPC_FAULT_TODO); + } + + /* call the dispatch function */ + status = call->dce->dispatch[opnum](call->dce, call->mem_ctx, r); + if (!NT_STATUS_IS_OK(status)) { + return dcesrv_fault(call, DCERPC_FAULT_TODO); + } + + /* form the reply NDR */ + push = ndr_push_init_ctx(call->mem_ctx); + if (!push) { + return NT_STATUS_NO_MEMORY; + } + + status = call->dce->ndr->calls[opnum].ndr_push(push, NDR_OUT, r); + if (!NT_STATUS_IS_OK(status)) { + return dcesrv_fault(call, DCERPC_FAULT_TODO); + } + + stub = ndr_push_blob(push); + + /* form the dcerpc response packet */ + pkt.rpc_vers = 5; + pkt.rpc_vers_minor = 0; + pkt.drep[0] = 0x10; /* Little endian */ + pkt.drep[1] = 0; + pkt.drep[2] = 0; + pkt.drep[3] = 0; + pkt.auth_length = 0; + pkt.call_id = call->pkt.call_id; + pkt.ptype = DCERPC_PKT_RESPONSE; + pkt.pfc_flags = DCERPC_PFC_FLAG_FIRST | DCERPC_PFC_FLAG_LAST; + pkt.u.response.alloc_hint = stub.length; + pkt.u.response.context_id = call->pkt.u.request.context_id; + pkt.u.response.cancel_count = 0; + pkt.u.response.stub_and_verifier = stub; + + push = ndr_push_init_ctx(call->mem_ctx); + if (!push) { + return NT_STATUS_NO_MEMORY; + } + + status = ndr_push_dcerpc_packet(push, NDR_SCALARS|NDR_BUFFERS, &pkt); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + call->data = ndr_push_blob(push); + SSVAL(call->data.data, DCERPC_FRAG_LEN_OFFSET, call->data.length); + + return NT_STATUS_OK; +} + /* provide some input to a dcerpc endpoint server. This passes data from a dcerpc client into the server */ -NTSTATUS dcesrv_input(struct dcesrv_state *p, const DATA_BLOB *data) +NTSTATUS dcesrv_input(struct dcesrv_state *dce, const DATA_BLOB *data) { - return NT_STATUS_NOT_IMPLEMENTED; + struct ndr_pull *ndr; + TALLOC_CTX *mem_ctx; + NTSTATUS status; + struct dcesrv_call_state *call; + + mem_ctx = talloc_init("dcesrv_input"); + if (!mem_ctx) { + return NT_STATUS_NO_MEMORY; + } + call = talloc(mem_ctx, sizeof(*call)); + if (!call) { + talloc_destroy(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + call->mem_ctx = mem_ctx; + call->dce = dce; + + ndr = ndr_pull_init_blob(data, mem_ctx); + if (!ndr) { + talloc_destroy(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + status = ndr_pull_dcerpc_packet(ndr, NDR_SCALARS|NDR_BUFFERS, &call->pkt); + if (!NT_STATUS_IS_OK(status)) { + talloc_destroy(mem_ctx); + return status; + } + + /* TODO: at this point we should see if the packet is a + continuation of an existing call, but I'm too lazy for that + right now ... maybe tomorrow */ + + + switch (call->pkt.ptype) { + case DCERPC_PKT_BIND: + status = dcesrv_bind(call); + break; + case DCERPC_PKT_REQUEST: + status = dcesrv_request(call); + break; + default: + status = NT_STATUS_INVALID_PARAMETER; + break; + } + + /* if we are going to be sending a reply then add + it to the list of pending calls. We add it to the end to keep the call + list in the order we will answer */ + if (NT_STATUS_IS_OK(status)) { + DLIST_ADD_END(dce->call_list, call, struct dcesrv_call_state *); + } else { + talloc_destroy(mem_ctx); + } + + return status; } /* retrieve some output from a dcerpc server. The amount of data that - is wanted is in data->length + is wanted is in data->length and data->data is already allocated + to hold that much data. */ -NTSTATUS dcesrv_output(struct dcesrv_state *p, DATA_BLOB *data) +NTSTATUS dcesrv_output(struct dcesrv_state *dce, DATA_BLOB *data) { - return NT_STATUS_NOT_IMPLEMENTED; + struct dcesrv_call_state *call; + + call = dce->call_list; + if (!call) { + return NT_STATUS_FOOBAR; + } + + if (data->length >= call->data.length) { + data->length = call->data.length; + } + + memcpy(data->data, call->data.data, data->length); + call->data.length -= data->length; + call->data.data += data->length; + + if (call->data.length == 0) { + /* we're done with this call */ + DLIST_REMOVE(dce->call_list, call); + talloc_destroy(call->mem_ctx); + } + + return NT_STATUS_OK; } diff --git a/source4/rpc_server/dcerpc_server.h b/source4/rpc_server/dcerpc_server.h index b28f40768a..9895254cad 100644 --- a/source4/rpc_server/dcerpc_server.h +++ b/source4/rpc_server/dcerpc_server.h @@ -32,6 +32,19 @@ struct dcesrv_endpoint { } info; }; +struct dcesrv_state; + +/* the dispatch functions for an interface take this form */ +typedef NTSTATUS (*dcesrv_dispatch_fn_t)(struct dcesrv_state *, TALLOC_CTX *, void *); + +/* the state of an ongoing dcerpc call */ +struct dcesrv_call_state { + struct dcesrv_call_state *next, *prev; + struct dcesrv_state *dce; + TALLOC_CTX *mem_ctx; + struct dcerpc_packet pkt; + DATA_BLOB data; +}; /* the state associated with a dcerpc server connection */ struct dcesrv_state { @@ -43,15 +56,29 @@ struct dcesrv_state { /* endpoint operations provided by the endpoint server */ const struct dcesrv_endpoint_ops *ops; + /* the ndr function table for the chosen interface */ + const struct dcerpc_interface_table *ndr; + + /* the dispatch table for the chosen interface. Must contain + enough entries for all entries in the ndr table */ + const dcesrv_dispatch_fn_t *dispatch; + + /* the state of the current calls */ + struct dcesrv_call_state *call_list; + /* private data for the endpoint server */ void *private; }; struct dcesrv_endpoint_ops { - /* the query function is used to ask an endpoint server if it + /* this function is used to ask an endpoint server if it handles a particular endpoint */ - BOOL (*query)(const struct dcesrv_endpoint *); + BOOL (*query_endpoint)(const struct dcesrv_endpoint *); + + /* this function sets up the dispatch table for this + connection */ + BOOL (*set_interface)(struct dcesrv_state *, const char *, uint32); /* connect() is called when a connection is made to an endpoint */ NTSTATUS (*connect)(struct dcesrv_state *); diff --git a/source4/rpc_server/rpc_echo.c b/source4/rpc_server/rpc_echo.c index 51914f7946..37b72f764b 100644 --- a/source4/rpc_server/rpc_echo.c +++ b/source4/rpc_server/rpc_echo.c @@ -23,20 +23,94 @@ #include "includes.h" +static NTSTATUS echo_AddOne(struct dcesrv_state *dce, TALLOC_CTX *mem_ctx, struct echo_AddOne *r) +{ + *r->out.v = *r->in.v + 1; + return NT_STATUS_OK; +} + +static NTSTATUS echo_EchoData(struct dcesrv_state *dce, TALLOC_CTX *mem_ctx, struct echo_EchoData *r) +{ + r->out.out_data = talloc(mem_ctx, r->in.len); + if (!r->out.out_data) { + return NT_STATUS_NO_MEMORY; + } + memcpy(r->out.out_data, r->in.in_data, r->in.len); + + return NT_STATUS_OK; +} + +static NTSTATUS echo_SinkData(struct dcesrv_state *dce, TALLOC_CTX *mem_ctx, struct echo_SinkData *r) +{ + return NT_STATUS_OK; +} + +static NTSTATUS echo_SourceData(struct dcesrv_state *dce, TALLOC_CTX *mem_ctx, struct echo_SourceData *r) +{ + int i; + r->out.data = talloc(mem_ctx, r->in.len); + if (!r->out.data) { + return NT_STATUS_NO_MEMORY; + } + for (i=0;iin.len;i++) { + r->out.data[i] = i; + } + + return NT_STATUS_OK; +} + +static NTSTATUS echo_TestCall(struct dcesrv_state *dce, TALLOC_CTX *mem_ctx, struct TestCall *r) +{ + return NT_STATUS_BAD_NETWORK_NAME; +} + +static NTSTATUS echo_TestCall2(struct dcesrv_state *dce, TALLOC_CTX *mem_ctx, struct TestCall2 *r) +{ + return NT_STATUS_BAD_NETWORK_NAME; +} + + + /************************************************************************** all the code below this point is boilerplate that will be auto-generated ***************************************************************************/ +static const dcesrv_dispatch_fn_t dispatch_table[] = { + (dcesrv_dispatch_fn_t)echo_AddOne, + (dcesrv_dispatch_fn_t)echo_EchoData, + (dcesrv_dispatch_fn_t)echo_SinkData, + (dcesrv_dispatch_fn_t)echo_SourceData, + (dcesrv_dispatch_fn_t)echo_TestCall, + (dcesrv_dispatch_fn_t)echo_TestCall2 +}; + /* return True if we want to handle the given endpoint */ -static BOOL op_query(const struct dcesrv_endpoint *ep) +static BOOL op_query_endpoint(const struct dcesrv_endpoint *ep) { return dcesrv_table_query(&dcerpc_table_rpcecho, ep); } +/* + setup for a particular rpc interface +*/ +static BOOL op_set_interface(struct dcesrv_state *dce, const char *uuid, uint32 if_version) +{ + if (strcasecmp(uuid, dcerpc_table_rpcecho.uuid) != 0 || + if_version != dcerpc_table_rpcecho.if_version) { + DEBUG(2,("Attempt to use unknown interface %s/%d\n", uuid, if_version)); + return False; + } + + dce->ndr = &dcerpc_table_rpcecho; + dce->dispatch = dispatch_table; + + return True; +} + /* op_connect is called when a connection is made to an endpoint */ static NTSTATUS op_connect(struct dcesrv_state *dce) @@ -51,7 +125,8 @@ static void op_disconnect(struct dcesrv_state *dce) static const struct dcesrv_endpoint_ops rpc_echo_ops = { - op_query, + op_query_endpoint, + op_set_interface, op_connect, op_disconnect }; -- cgit