summaryrefslogtreecommitdiffstats
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.c2630
1 files changed, 2630 insertions, 0 deletions
diff --git a/source4/librpc/rpc/dcerpc.c b/source4/librpc/rpc/dcerpc.c
new file mode 100644
index 0000000..baf6df6
--- /dev/null
+++ b/source4/librpc/rpc/dcerpc.c
@@ -0,0 +1,2630 @@
+/*
+ Unix SMB/CIFS implementation.
+ raw dcerpc operations
+
+ Copyright (C) Tim Potter 2003
+ Copyright (C) Andrew Tridgell 2003-2005
+ Copyright (C) Jelmer Vernooij 2004-2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "../lib/util/dlinklist.h"
+#include "lib/events/events.h"
+#include "librpc/rpc/dcerpc.h"
+#include "librpc/rpc/dcerpc_proto.h"
+#include "librpc/rpc/dcerpc_util.h"
+#include "librpc/rpc/dcerpc_pkt_auth.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_dcerpc.h"
+#include "auth/gensec/gensec.h"
+#include "param/param.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "librpc/rpc/rpc_common.h"
+#include "lib/tsocket/tsocket.h"
+#include "libcli/smb/tstream_smbXcli_np.h"
+
+
+enum rpc_request_state {
+ RPC_REQUEST_QUEUED,
+ RPC_REQUEST_PENDING,
+ RPC_REQUEST_DONE
+};
+
+/*
+ handle for an async dcerpc request
+*/
+struct rpc_request {
+ struct rpc_request *next, *prev;
+ struct dcerpc_pipe *p;
+ NTSTATUS status;
+ uint32_t call_id;
+ enum rpc_request_state state;
+ DATA_BLOB payload;
+ uint32_t flags;
+ uint32_t fault_code;
+
+ /* this is used to distinguish bind and alter_context requests
+ from normal requests */
+ void (*recv_handler)(struct rpc_request *conn,
+ DATA_BLOB *blob, struct ncacn_packet *pkt);
+
+ const struct GUID *object;
+ uint16_t opnum;
+ DATA_BLOB request_data;
+ bool ignore_timeout;
+ bool wait_for_sync;
+ bool verify_bitmask1;
+ bool verify_pcontext;
+
+ struct {
+ void (*callback)(struct rpc_request *);
+ void *private_data;
+ } async;
+};
+
+_PUBLIC_ NTSTATUS dcerpc_init(void)
+{
+ return gensec_init();
+}
+
+static void dcerpc_connection_dead(struct dcecli_connection *conn, NTSTATUS status);
+static void dcerpc_schedule_io_trigger(struct dcecli_connection *c);
+
+static struct rpc_request *dcerpc_request_send(TALLOC_CTX *mem_ctx,
+ struct dcerpc_pipe *p,
+ const struct GUID *object,
+ uint16_t opnum,
+ DATA_BLOB *stub_data);
+static NTSTATUS dcerpc_request_recv(struct rpc_request *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *stub_data);
+static NTSTATUS dcerpc_ndr_validate_in(struct dcecli_connection *c,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB blob,
+ size_t struct_size,
+ ndr_push_flags_fn_t ndr_push,
+ ndr_pull_flags_fn_t ndr_pull);
+static NTSTATUS dcerpc_ndr_validate_out(struct dcecli_connection *c,
+ struct ndr_pull *pull_in,
+ void *struct_ptr,
+ size_t struct_size,
+ ndr_push_flags_fn_t ndr_push,
+ ndr_pull_flags_fn_t ndr_pull,
+ ndr_print_function_t ndr_print);
+static NTSTATUS dcerpc_shutdown_pipe(struct dcecli_connection *p, NTSTATUS status);
+static NTSTATUS dcerpc_send_request(struct dcecli_connection *p, DATA_BLOB *data,
+ bool trigger_read);
+static NTSTATUS dcerpc_send_read(struct dcecli_connection *p);
+
+/* destroy a dcerpc connection */
+static int dcerpc_connection_destructor(struct dcecli_connection *conn)
+{
+ if (conn->dead) {
+ conn->free_skipped = true;
+ return -1;
+ }
+ dcerpc_connection_dead(conn, NT_STATUS_LOCAL_DISCONNECT);
+ return 0;
+}
+
+
+/* initialise a dcerpc connection.
+ the event context is optional
+*/
+static struct dcecli_connection *dcerpc_connection_init(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev)
+{
+ struct dcecli_connection *c;
+
+ c = talloc_zero(mem_ctx, struct dcecli_connection);
+ if (!c) {
+ return NULL;
+ }
+
+ c->event_ctx = ev;
+
+ if (c->event_ctx == NULL) {
+ talloc_free(c);
+ return NULL;
+ }
+
+ c->call_id = 1;
+ c->security_state.auth_type = DCERPC_AUTH_TYPE_NONE;
+ c->security_state.auth_level = DCERPC_AUTH_LEVEL_NONE;
+ c->security_state.auth_context_id = 0;
+ c->security_state.session_key = dcecli_generic_session_key;
+ c->security_state.generic_state = NULL;
+ c->flags = 0;
+ /*
+ * Windows uses 5840 for ncacn_ip_tcp,
+ * so we also use it (for every transport)
+ * by default. But we give the transport
+ * the chance to overwrite it.
+ */
+ c->srv_max_xmit_frag = 5840;
+ c->srv_max_recv_frag = 5840;
+ c->max_total_response_size = DCERPC_NCACN_RESPONSE_DEFAULT_MAX_SIZE;
+ c->pending = NULL;
+
+ c->io_trigger = tevent_create_immediate(c);
+ if (c->io_trigger == NULL) {
+ talloc_free(c);
+ return NULL;
+ }
+
+ talloc_set_destructor(c, dcerpc_connection_destructor);
+
+ return c;
+}
+
+struct dcerpc_bh_state {
+ struct dcerpc_pipe *p;
+};
+
+static bool dcerpc_bh_is_connected(struct dcerpc_binding_handle *h)
+{
+ struct dcerpc_bh_state *hs = dcerpc_binding_handle_data(h,
+ struct dcerpc_bh_state);
+
+ if (!hs->p) {
+ return false;
+ }
+
+ if (!hs->p->conn) {
+ return false;
+ }
+
+ if (hs->p->conn->dead) {
+ return false;
+ }
+
+ return true;
+}
+
+static uint32_t dcerpc_bh_set_timeout(struct dcerpc_binding_handle *h,
+ uint32_t timeout)
+{
+ struct dcerpc_bh_state *hs = dcerpc_binding_handle_data(h,
+ struct dcerpc_bh_state);
+ uint32_t old;
+
+ if (!hs->p) {
+ return DCERPC_REQUEST_TIMEOUT;
+ }
+
+ old = hs->p->request_timeout;
+ hs->p->request_timeout = timeout;
+
+ return old;
+}
+
+static void dcerpc_bh_auth_info(struct dcerpc_binding_handle *h,
+ enum dcerpc_AuthType *auth_type,
+ enum dcerpc_AuthLevel *auth_level)
+{
+ struct dcerpc_bh_state *hs = dcerpc_binding_handle_data(h,
+ struct dcerpc_bh_state);
+
+ if (hs->p == NULL) {
+ return;
+ }
+
+ if (hs->p->conn == NULL) {
+ return;
+ }
+
+ *auth_type = hs->p->conn->security_state.auth_type;
+ *auth_level = hs->p->conn->security_state.auth_level;
+}
+
+struct dcerpc_bh_raw_call_state {
+ struct tevent_context *ev;
+ struct dcerpc_binding_handle *h;
+ DATA_BLOB in_data;
+ DATA_BLOB out_data;
+ uint32_t out_flags;
+};
+
+static void dcerpc_bh_raw_call_done(struct rpc_request *subreq);
+
+static struct tevent_req *dcerpc_bh_raw_call_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct dcerpc_binding_handle *h,
+ const struct GUID *object,
+ uint32_t opnum,
+ uint32_t in_flags,
+ const uint8_t *in_data,
+ size_t in_length)
+{
+ struct dcerpc_bh_state *hs = dcerpc_binding_handle_data(h,
+ struct dcerpc_bh_state);
+ struct tevent_req *req;
+ struct dcerpc_bh_raw_call_state *state;
+ bool ok;
+ struct rpc_request *subreq;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct dcerpc_bh_raw_call_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->h = h;
+ state->in_data.data = discard_const_p(uint8_t, in_data);
+ state->in_data.length = in_length;
+
+ ok = dcerpc_bh_is_connected(h);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_CONNECTION_DISCONNECTED);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = dcerpc_request_send(state,
+ hs->p,
+ object,
+ opnum,
+ &state->in_data);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ subreq->async.callback = dcerpc_bh_raw_call_done;
+ subreq->async.private_data = req;
+
+ return req;
+}
+
+static void dcerpc_bh_raw_call_done(struct rpc_request *subreq)
+{
+ struct tevent_req *req =
+ talloc_get_type_abort(subreq->async.private_data,
+ struct tevent_req);
+ struct dcerpc_bh_raw_call_state *state =
+ tevent_req_data(req,
+ struct dcerpc_bh_raw_call_state);
+ NTSTATUS status;
+ uint32_t fault_code;
+
+ state->out_flags = 0;
+ if (subreq->flags & DCERPC_PULL_BIGENDIAN) {
+ state->out_flags |= LIBNDR_FLAG_BIGENDIAN;
+ }
+
+ fault_code = subreq->fault_code;
+
+ status = dcerpc_request_recv(subreq, state, &state->out_data);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NET_WRITE_FAULT)) {
+ status = dcerpc_fault_to_nt_status(fault_code);
+ }
+
+ /*
+ * We trigger the callback in the next event run
+ * because the code in this file might trigger
+ * multiple request callbacks from within a single
+ * while loop.
+ *
+ * In order to avoid segfaults from within
+ * dcerpc_connection_dead() we call
+ * tevent_req_defer_callback().
+ */
+ tevent_req_defer_callback(req, state->ev);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS dcerpc_bh_raw_call_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **out_data,
+ size_t *out_length,
+ uint32_t *out_flags)
+{
+ struct dcerpc_bh_raw_call_state *state =
+ tevent_req_data(req,
+ struct dcerpc_bh_raw_call_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *out_data = talloc_move(mem_ctx, &state->out_data.data);
+ *out_length = state->out_data.length;
+ *out_flags = state->out_flags;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct dcerpc_bh_disconnect_state {
+ uint8_t _dummy;
+};
+
+static struct tevent_req *dcerpc_bh_disconnect_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct dcerpc_binding_handle *h)
+{
+ struct dcerpc_bh_state *hs = dcerpc_binding_handle_data(h,
+ struct dcerpc_bh_state);
+ struct tevent_req *req;
+ struct dcerpc_bh_disconnect_state *state;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct dcerpc_bh_disconnect_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ ok = dcerpc_bh_is_connected(h);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_CONNECTION_DISCONNECTED);
+ return tevent_req_post(req, ev);
+ }
+
+ /* TODO: do a real disconnect ... */
+ hs->p = NULL;
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static NTSTATUS dcerpc_bh_disconnect_recv(struct tevent_req *req)
+{
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+static bool dcerpc_bh_push_bigendian(struct dcerpc_binding_handle *h)
+{
+ struct dcerpc_bh_state *hs = dcerpc_binding_handle_data(h,
+ struct dcerpc_bh_state);
+
+ if (hs->p->conn->flags & DCERPC_PUSH_BIGENDIAN) {
+ return true;
+ }
+
+ return false;
+}
+
+static bool dcerpc_bh_ref_alloc(struct dcerpc_binding_handle *h)
+{
+ struct dcerpc_bh_state *hs = dcerpc_binding_handle_data(h,
+ struct dcerpc_bh_state);
+
+ if (hs->p->conn->flags & DCERPC_NDR_REF_ALLOC) {
+ return true;
+ }
+
+ return false;
+}
+
+static bool dcerpc_bh_use_ndr64(struct dcerpc_binding_handle *h)
+{
+ struct dcerpc_bh_state *hs = dcerpc_binding_handle_data(h,
+ struct dcerpc_bh_state);
+
+ if (hs->p->conn->flags & DCERPC_NDR64) {
+ return true;
+ }
+
+ return false;
+}
+
+static void dcerpc_bh_do_ndr_print(struct dcerpc_binding_handle *h,
+ int ndr_flags,
+ const void *_struct_ptr,
+ const struct ndr_interface_call *call)
+{
+ struct dcerpc_bh_state *hs = dcerpc_binding_handle_data(h,
+ struct dcerpc_bh_state);
+ void *struct_ptr = discard_const(_struct_ptr);
+ bool print_in = false;
+ bool print_out = false;
+
+ if (hs->p->conn->flags & DCERPC_DEBUG_PRINT_IN) {
+ print_in = true;
+ }
+
+ if (hs->p->conn->flags & DCERPC_DEBUG_PRINT_OUT) {
+ print_out = true;
+ }
+
+ if (DEBUGLEVEL >= 11) {
+ print_in = true;
+ print_out = true;
+ }
+
+ if (ndr_flags & NDR_IN) {
+ if (print_in) {
+ ndr_print_function_debug(call->ndr_print,
+ call->name,
+ ndr_flags,
+ struct_ptr);
+ }
+ }
+ if (ndr_flags & NDR_OUT) {
+ if (print_out) {
+ ndr_print_function_debug(call->ndr_print,
+ call->name,
+ ndr_flags,
+ struct_ptr);
+ }
+ }
+}
+
+static void dcerpc_bh_ndr_push_failed(struct dcerpc_binding_handle *h,
+ NTSTATUS error,
+ const void *struct_ptr,
+ const struct ndr_interface_call *call)
+{
+ DEBUG(2,("Unable to ndr_push structure for %s - %s\n",
+ call->name, nt_errstr(error)));
+}
+
+static void dcerpc_bh_ndr_pull_failed(struct dcerpc_binding_handle *h,
+ NTSTATUS error,
+ const DATA_BLOB *blob,
+ const struct ndr_interface_call *call)
+{
+ struct dcerpc_bh_state *hs = dcerpc_binding_handle_data(h,
+ struct dcerpc_bh_state);
+ const uint32_t num_examples = 20;
+ uint32_t i;
+
+ DEBUG(2,("Unable to ndr_pull structure for %s - %s\n",
+ call->name, nt_errstr(error)));
+
+ if (hs->p->conn->packet_log_dir == NULL) return;
+
+ for (i=0;i<num_examples;i++) {
+ char *name=NULL;
+ int ret;
+
+ ret = asprintf(&name, "%s/rpclog/%s-out.%d",
+ hs->p->conn->packet_log_dir,
+ call->name, i);
+ if (ret == -1) {
+ return;
+ }
+ if (!file_exist(name)) {
+ if (file_save(name, blob->data, blob->length)) {
+ DEBUG(10,("Logged rpc packet to %s\n", name));
+ }
+ free(name);
+ break;
+ }
+ free(name);
+ }
+}
+
+static NTSTATUS dcerpc_bh_ndr_validate_in(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ const DATA_BLOB *blob,
+ const struct ndr_interface_call *call)
+{
+ struct dcerpc_bh_state *hs = dcerpc_binding_handle_data(h,
+ struct dcerpc_bh_state);
+
+ if (hs->p->conn->flags & DCERPC_DEBUG_VALIDATE_IN) {
+ NTSTATUS status;
+
+ status = dcerpc_ndr_validate_in(hs->p->conn,
+ mem_ctx,
+ *blob,
+ call->struct_size,
+ call->ndr_push,
+ call->ndr_pull);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Validation [in] failed for %s - %s\n",
+ call->name, nt_errstr(status)));
+ return status;
+ }
+ }
+
+ DEBUG(10,("rpc request data:\n"));
+ dump_data(10, blob->data, blob->length);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS dcerpc_bh_ndr_validate_out(struct dcerpc_binding_handle *h,
+ struct ndr_pull *pull_in,
+ const void *_struct_ptr,
+ const struct ndr_interface_call *call)
+{
+ struct dcerpc_bh_state *hs = dcerpc_binding_handle_data(h,
+ struct dcerpc_bh_state);
+ void *struct_ptr = discard_const(_struct_ptr);
+
+ DEBUG(10,("rpc reply data:\n"));
+ dump_data(10, pull_in->data, pull_in->data_size);
+
+ if (pull_in->offset != pull_in->data_size) {
+ DEBUG(0,("Warning! ignoring %u unread bytes at ofs:%u (0x%08X) for %s!\n",
+ pull_in->data_size - pull_in->offset,
+ pull_in->offset, pull_in->offset,
+ call->name));
+ /* we used to return NT_STATUS_INFO_LENGTH_MISMATCH here,
+ but it turns out that early versions of NT
+ (specifically NT3.1) add junk onto the end of rpc
+ packets, so if we want to interoperate at all with
+ those versions then we need to ignore this error */
+ }
+
+ if (hs->p->conn->flags & DCERPC_DEBUG_VALIDATE_OUT) {
+ NTSTATUS status;
+
+ status = dcerpc_ndr_validate_out(hs->p->conn,
+ pull_in,
+ struct_ptr,
+ call->struct_size,
+ call->ndr_push,
+ call->ndr_pull,
+ call->ndr_print);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2,("Validation [out] failed for %s - %s\n",
+ call->name, nt_errstr(status)));
+ return status;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+static const struct dcerpc_binding_handle_ops dcerpc_bh_ops = {
+ .name = "dcerpc",
+ .is_connected = dcerpc_bh_is_connected,
+ .set_timeout = dcerpc_bh_set_timeout,
+ .auth_info = dcerpc_bh_auth_info,
+ .raw_call_send = dcerpc_bh_raw_call_send,
+ .raw_call_recv = dcerpc_bh_raw_call_recv,
+ .disconnect_send = dcerpc_bh_disconnect_send,
+ .disconnect_recv = dcerpc_bh_disconnect_recv,
+
+ .push_bigendian = dcerpc_bh_push_bigendian,
+ .ref_alloc = dcerpc_bh_ref_alloc,
+ .use_ndr64 = dcerpc_bh_use_ndr64,
+ .do_ndr_print = dcerpc_bh_do_ndr_print,
+ .ndr_push_failed = dcerpc_bh_ndr_push_failed,
+ .ndr_pull_failed = dcerpc_bh_ndr_pull_failed,
+ .ndr_validate_in = dcerpc_bh_ndr_validate_in,
+ .ndr_validate_out = dcerpc_bh_ndr_validate_out,
+};
+
+/* initialise a dcerpc pipe. */
+struct dcerpc_binding_handle *dcerpc_pipe_binding_handle(struct dcerpc_pipe *p,
+ const struct GUID *object,
+ const struct ndr_interface_table *table)
+{
+ struct dcerpc_binding_handle *h;
+ struct dcerpc_bh_state *hs;
+
+ h = dcerpc_binding_handle_create(p,
+ &dcerpc_bh_ops,
+ object,
+ table,
+ &hs,
+ struct dcerpc_bh_state,
+ __location__);
+ if (h == NULL) {
+ return NULL;
+ }
+ hs->p = p;
+
+ dcerpc_binding_handle_set_sync_ev(h, p->conn->event_ctx);
+
+ return h;
+}
+
+/* initialise a dcerpc pipe. */
+_PUBLIC_ struct dcerpc_pipe *dcerpc_pipe_init(TALLOC_CTX *mem_ctx, struct tevent_context *ev)
+{
+ struct dcerpc_pipe *p;
+
+ p = talloc_zero(mem_ctx, struct dcerpc_pipe);
+ if (!p) {
+ return NULL;
+ }
+
+ p->conn = dcerpc_connection_init(p, ev);
+ if (p->conn == NULL) {
+ talloc_free(p);
+ return NULL;
+ }
+
+ p->request_timeout = DCERPC_REQUEST_TIMEOUT;
+
+ if (DEBUGLVL(100)) {
+ p->conn->flags |= DCERPC_DEBUG_PRINT_BOTH;
+ }
+
+ return p;
+}
+
+
+/*
+ choose the next call id to use
+*/
+static uint32_t next_call_id(struct dcecli_connection *c)
+{
+ c->call_id++;
+ if (c->call_id == 0) {
+ c->call_id++;
+ }
+ return c->call_id;
+}
+
+/**
+ setup for a ndr pull, also setting up any flags from the binding string
+*/
+static struct ndr_pull *ndr_pull_init_flags(struct dcecli_connection *c,
+ DATA_BLOB *blob, TALLOC_CTX *mem_ctx)
+{
+ struct ndr_pull *ndr = ndr_pull_init_blob(blob, mem_ctx);
+
+ if (ndr == NULL) return ndr;
+
+ if (c->flags & DCERPC_DEBUG_PAD_CHECK) {
+ ndr->flags |= LIBNDR_FLAG_PAD_CHECK;
+ }
+
+ if (c->flags & DCERPC_NDR_REF_ALLOC) {
+ ndr->flags |= LIBNDR_FLAG_REF_ALLOC;
+ }
+
+ if (c->flags & DCERPC_NDR64) {
+ ndr->flags |= LIBNDR_FLAG_NDR64;
+ }
+
+ return ndr;
+}
+
+/*
+ parse the authentication information on a dcerpc response packet
+*/
+static NTSTATUS ncacn_pull_pkt_auth(struct dcecli_connection *c,
+ TALLOC_CTX *mem_ctx,
+ enum dcerpc_pkt_type ptype,
+ uint8_t required_flags,
+ uint8_t optional_flags,
+ uint8_t payload_offset,
+ DATA_BLOB *payload_and_verifier,
+ DATA_BLOB *raw_packet,
+ const struct ncacn_packet *pkt)
+{
+ const struct dcerpc_auth tmp_auth = {
+ .auth_type = c->security_state.auth_type,
+ .auth_level = c->security_state.auth_level,
+ .auth_context_id = c->security_state.auth_context_id,
+ };
+ NTSTATUS status;
+
+ status = dcerpc_ncacn_pull_pkt_auth(&tmp_auth,
+ c->security_state.generic_state,
+ true, /* check_pkt_auth_fields */
+ mem_ctx,
+ ptype,
+ required_flags,
+ optional_flags,
+ payload_offset,
+ payload_and_verifier,
+ raw_packet,
+ pkt);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROTOCOL_ERROR)) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ push a dcerpc request packet into a blob, possibly signing it.
+*/
+static NTSTATUS ncacn_push_request_sign(struct dcecli_connection *c,
+ DATA_BLOB *blob, TALLOC_CTX *mem_ctx,
+ size_t sig_size,
+ struct ncacn_packet *pkt)
+{
+ const struct dcerpc_auth tmp_auth = {
+ .auth_type = c->security_state.auth_type,
+ .auth_level = c->security_state.auth_level,
+ .auth_context_id = c->security_state.auth_context_id,
+ };
+ NTSTATUS status;
+ uint8_t payload_offset = DCERPC_REQUEST_LENGTH;
+
+ if (pkt->pfc_flags & DCERPC_PFC_FLAG_OBJECT_UUID) {
+ payload_offset += 16;
+ }
+
+ status = dcerpc_ncacn_push_pkt_auth(&tmp_auth,
+ c->security_state.generic_state,
+ mem_ctx, blob,
+ sig_size,
+ payload_offset,
+ &pkt->u.request.stub_and_verifier,
+ pkt);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ fill in the fixed values in a dcerpc header
+*/
+static void init_ncacn_hdr(struct dcecli_connection *c, struct ncacn_packet *pkt)
+{
+ pkt->rpc_vers = 5;
+ pkt->rpc_vers_minor = 0;
+ if (c->flags & DCERPC_PUSH_BIGENDIAN) {
+ pkt->drep[0] = 0;
+ } else {
+ pkt->drep[0] = DCERPC_DREP_LE;
+ }
+ pkt->drep[1] = 0;
+ pkt->drep[2] = 0;
+ pkt->drep[3] = 0;
+}
+
+/*
+ map a bind nak reason to a NTSTATUS
+*/
+static NTSTATUS dcerpc_map_nak_reason(enum dcerpc_bind_nak_reason reason)
+{
+ switch (reason) {
+ case DCERPC_BIND_NAK_REASON_PROTOCOL_VERSION_NOT_SUPPORTED:
+ return NT_STATUS_REVISION_MISMATCH;
+ case DCERPC_BIND_NAK_REASON_INVALID_AUTH_TYPE:
+ return NT_STATUS_INVALID_PARAMETER;
+ default:
+ break;
+ }
+ return NT_STATUS_UNSUCCESSFUL;
+}
+
+static NTSTATUS dcerpc_map_ack_reason(const struct dcerpc_ack_ctx *ack)
+{
+ if (ack == NULL) {
+ return NT_STATUS_RPC_PROTOCOL_ERROR;
+ }
+
+ switch (ack->result) {
+ case DCERPC_BIND_ACK_RESULT_NEGOTIATE_ACK:
+ /*
+ * We have not asked for this...
+ */
+ return NT_STATUS_RPC_PROTOCOL_ERROR;
+ default:
+ break;
+ }
+
+ switch (ack->reason.value) {
+ case DCERPC_BIND_ACK_REASON_ABSTRACT_SYNTAX_NOT_SUPPORTED:
+ return NT_STATUS_RPC_UNSUPPORTED_NAME_SYNTAX;
+ case DCERPC_BIND_ACK_REASON_TRANSFER_SYNTAXES_NOT_SUPPORTED:
+ return NT_STATUS_RPC_UNSUPPORTED_NAME_SYNTAX;
+ default:
+ break;
+ }
+ return NT_STATUS_UNSUCCESSFUL;
+}
+
+/*
+ remove requests from the pending or queued queues
+ */
+static int dcerpc_req_dequeue(struct rpc_request *req)
+{
+ switch (req->state) {
+ case RPC_REQUEST_QUEUED:
+ DLIST_REMOVE(req->p->conn->request_queue, req);
+ break;
+ case RPC_REQUEST_PENDING:
+ DLIST_REMOVE(req->p->conn->pending, req);
+ break;
+ case RPC_REQUEST_DONE:
+ break;
+ }
+ return 0;
+}
+
+
+/*
+ mark the dcerpc connection dead. All outstanding requests get an error
+*/
+static void dcerpc_connection_dead(struct dcecli_connection *conn, NTSTATUS status)
+{
+ if (conn->dead) return;
+
+ conn->dead = true;
+
+ TALLOC_FREE(conn->io_trigger);
+ conn->io_trigger_pending = false;
+
+ dcerpc_shutdown_pipe(conn, status);
+
+ /* all pending requests get the error */
+ while (conn->pending) {
+ struct rpc_request *req = conn->pending;
+ dcerpc_req_dequeue(req);
+ req->state = RPC_REQUEST_DONE;
+ req->status = status;
+ if (req->async.callback) {
+ req->async.callback(req);
+ }
+ }
+
+ /* all requests, which are not shipped */
+ while (conn->request_queue) {
+ struct rpc_request *req = conn->request_queue;
+ dcerpc_req_dequeue(req);
+ req->state = RPC_REQUEST_DONE;
+ req->status = status;
+ if (req->async.callback) {
+ req->async.callback(req);
+ }
+ }
+
+ talloc_set_destructor(conn, NULL);
+ if (conn->free_skipped) {
+ talloc_free(conn);
+ }
+}
+
+/*
+ forward declarations of the recv_data handlers for the types of
+ packets we need to handle
+*/
+static void dcerpc_request_recv_data(struct dcecli_connection *c,
+ DATA_BLOB *raw_packet, struct ncacn_packet *pkt);
+
+/*
+ receive a dcerpc reply from the transport. Here we work out what
+ type of reply it is (normal request, bind or alter context) and
+ dispatch to the appropriate handler
+*/
+static void dcerpc_recv_data(struct dcecli_connection *conn, DATA_BLOB *blob, NTSTATUS status)
+{
+ struct ncacn_packet pkt;
+
+ if (conn->dead) {
+ return;
+ }
+
+ if (NT_STATUS_IS_OK(status) && blob->length == 0) {
+ status = NT_STATUS_UNEXPECTED_NETWORK_ERROR;
+ }
+
+ /* the transport may be telling us of a severe error, such as
+ a dropped socket */
+ if (!NT_STATUS_IS_OK(status)) {
+ data_blob_free(blob);
+ dcerpc_connection_dead(conn, status);
+ return;
+ }
+
+ /* parse the basic packet to work out what type of response this is */
+ status = dcerpc_pull_ncacn_packet(blob->data, blob, &pkt);
+ if (!NT_STATUS_IS_OK(status)) {
+ data_blob_free(blob);
+ dcerpc_connection_dead(conn, status);
+ return;
+ }
+
+ dcerpc_request_recv_data(conn, blob, &pkt);
+}
+
+/*
+ handle timeouts of individual dcerpc requests
+*/
+static void dcerpc_timeout_handler(struct tevent_context *ev, struct tevent_timer *te,
+ struct timeval t, void *private_data)
+{
+ struct rpc_request *req = talloc_get_type(private_data, struct rpc_request);
+
+ if (req->ignore_timeout) {
+ dcerpc_req_dequeue(req);
+ req->state = RPC_REQUEST_DONE;
+ req->status = NT_STATUS_IO_TIMEOUT;
+ if (req->async.callback) {
+ req->async.callback(req);
+ }
+ return;
+ }
+
+ dcerpc_connection_dead(req->p->conn, NT_STATUS_IO_TIMEOUT);
+}
+
+struct dcerpc_bind_state {
+ struct tevent_context *ev;
+ struct dcerpc_pipe *p;
+};
+
+static void dcerpc_bind_fail_handler(struct rpc_request *subreq);
+static void dcerpc_bind_recv_handler(struct rpc_request *subreq,
+ DATA_BLOB *raw_packet,
+ struct ncacn_packet *pkt);
+
+struct tevent_req *dcerpc_bind_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct dcerpc_pipe *p,
+ const struct ndr_syntax_id *syntax,
+ const struct ndr_syntax_id *transfer_syntax)
+{
+ struct tevent_req *req;
+ struct dcerpc_bind_state *state;
+ struct ncacn_packet pkt;
+ DATA_BLOB blob;
+ NTSTATUS status;
+ struct rpc_request *subreq;
+ uint32_t flags;
+ struct ndr_syntax_id bind_time_features;
+
+ bind_time_features = dcerpc_construct_bind_time_features(
+ DCERPC_BIND_TIME_SECURITY_CONTEXT_MULTIPLEXING |
+ DCERPC_BIND_TIME_KEEP_CONNECTION_ON_ORPHAN);
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct dcerpc_bind_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->p = p;
+
+ p->syntax = *syntax;
+ p->transfer_syntax = *transfer_syntax;
+
+ flags = dcerpc_binding_get_flags(p->binding);
+
+ init_ncacn_hdr(p->conn, &pkt);
+
+ pkt.ptype = DCERPC_PKT_BIND;
+ pkt.pfc_flags = DCERPC_PFC_FLAG_FIRST | DCERPC_PFC_FLAG_LAST;
+ pkt.call_id = p->conn->call_id;
+ pkt.auth_length = 0;
+
+ if (flags & DCERPC_CONCURRENT_MULTIPLEX) {
+ pkt.pfc_flags |= DCERPC_PFC_FLAG_CONC_MPX;
+ }
+
+ if (p->conn->flags & DCERPC_PROPOSE_HEADER_SIGNING) {
+ pkt.pfc_flags |= DCERPC_PFC_FLAG_SUPPORT_HEADER_SIGN;
+ }
+
+ pkt.u.bind.max_xmit_frag = p->conn->srv_max_xmit_frag;
+ pkt.u.bind.max_recv_frag = p->conn->srv_max_recv_frag;
+ pkt.u.bind.assoc_group_id = dcerpc_binding_get_assoc_group_id(p->binding);
+ pkt.u.bind.num_contexts = 2;
+ pkt.u.bind.ctx_list = talloc_zero_array(state, struct dcerpc_ctx_list,
+ pkt.u.bind.num_contexts);
+ if (tevent_req_nomem(pkt.u.bind.ctx_list, req)) {
+ return tevent_req_post(req, ev);
+ }
+ pkt.u.bind.ctx_list[0].context_id = p->context_id;
+ pkt.u.bind.ctx_list[0].num_transfer_syntaxes = 1;
+ pkt.u.bind.ctx_list[0].abstract_syntax = p->syntax;
+ pkt.u.bind.ctx_list[0].transfer_syntaxes = &p->transfer_syntax;
+ pkt.u.bind.ctx_list[1].context_id = p->context_id + 1;
+ pkt.u.bind.ctx_list[1].num_transfer_syntaxes = 1;
+ pkt.u.bind.ctx_list[1].abstract_syntax = p->syntax;
+ pkt.u.bind.ctx_list[1].transfer_syntaxes = &bind_time_features;
+ pkt.u.bind.auth_info = data_blob(NULL, 0);
+
+ /* construct the NDR form of the packet */
+ status = dcerpc_ncacn_push_auth(&blob,
+ state,
+ &pkt,
+ p->conn->security_state.tmp_auth_info.out);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * we allocate a dcerpc_request so we can be in the same
+ * request queue as normal requests
+ */
+ subreq = talloc_zero(state, struct rpc_request);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq->state = RPC_REQUEST_PENDING;
+ subreq->call_id = pkt.call_id;
+ subreq->async.private_data = req;
+ subreq->async.callback = dcerpc_bind_fail_handler;
+ subreq->p = p;
+ subreq->recv_handler = dcerpc_bind_recv_handler;
+ DLIST_ADD_END(p->conn->pending, subreq);
+ talloc_set_destructor(subreq, dcerpc_req_dequeue);
+
+ status = dcerpc_send_request(p->conn, &blob, true);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_add_timer(ev, subreq,
+ timeval_current_ofs(DCERPC_REQUEST_TIMEOUT, 0),
+ dcerpc_timeout_handler, subreq);
+
+ return req;
+}
+
+static void dcerpc_bind_fail_handler(struct rpc_request *subreq)
+{
+ struct tevent_req *req =
+ talloc_get_type_abort(subreq->async.private_data,
+ struct tevent_req);
+ struct dcerpc_bind_state *state =
+ tevent_req_data(req,
+ struct dcerpc_bind_state);
+ NTSTATUS status = subreq->status;
+
+ TALLOC_FREE(subreq);
+
+ /*
+ * We trigger the callback in the next event run
+ * because the code in this file might trigger
+ * multiple request callbacks from within a single
+ * while loop.
+ *
+ * In order to avoid segfaults from within
+ * dcerpc_connection_dead() we call
+ * tevent_req_defer_callback().
+ */
+ tevent_req_defer_callback(req, state->ev);
+
+ tevent_req_nterror(req, status);
+}
+
+static void dcerpc_bind_recv_handler(struct rpc_request *subreq,
+ DATA_BLOB *raw_packet,
+ struct ncacn_packet *pkt)
+{
+ struct tevent_req *req =
+ talloc_get_type_abort(subreq->async.private_data,
+ struct tevent_req);
+ struct dcerpc_bind_state *state =
+ tevent_req_data(req,
+ struct dcerpc_bind_state);
+ struct dcecli_connection *conn = state->p->conn;
+ struct dcecli_security *sec = &conn->security_state;
+ struct dcerpc_binding *b = NULL;
+ NTSTATUS status;
+ uint32_t flags;
+
+ /*
+ * Note that pkt is allocated under raw_packet->data,
+ * while raw_packet->data is a child of subreq.
+ */
+ talloc_steal(state, raw_packet->data);
+ TALLOC_FREE(subreq);
+
+ /*
+ * We trigger the callback in the next event run
+ * because the code in this file might trigger
+ * multiple request callbacks from within a single
+ * while loop.
+ *
+ * In order to avoid segfaults from within
+ * dcerpc_connection_dead() we call
+ * tevent_req_defer_callback().
+ */
+ tevent_req_defer_callback(req, state->ev);
+
+ if (pkt->ptype == DCERPC_PKT_BIND_NAK) {
+ status = dcerpc_map_nak_reason(pkt->u.bind_nak.reject_reason);
+
+ DEBUG(2,("dcerpc: bind_nak reason %d - %s\n",
+ pkt->u.bind_nak.reject_reason, nt_errstr(status)));
+
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ status = dcerpc_verify_ncacn_packet_header(pkt,
+ DCERPC_PKT_BIND_ACK,
+ pkt->u.bind_ack.auth_info.length,
+ DCERPC_PFC_FLAG_FIRST |
+ DCERPC_PFC_FLAG_LAST,
+ DCERPC_PFC_FLAG_CONC_MPX |
+ DCERPC_PFC_FLAG_SUPPORT_HEADER_SIGN);
+ if (!NT_STATUS_IS_OK(status)) {
+ state->p->last_fault_code = DCERPC_NCA_S_PROTO_ERROR;
+ tevent_req_nterror(req, NT_STATUS_NET_WRITE_FAULT);
+ return;
+ }
+
+ if (pkt->u.bind_ack.num_results < 1) {
+ state->p->last_fault_code = DCERPC_NCA_S_PROTO_ERROR;
+ tevent_req_nterror(req, NT_STATUS_NET_WRITE_FAULT);
+ return;
+ }
+
+ if (pkt->u.bind_ack.ctx_list[0].result != 0) {
+ status = dcerpc_map_ack_reason(&pkt->u.bind_ack.ctx_list[0]);
+ DEBUG(2,("dcerpc: bind_ack failed - reason %d - %s\n",
+ pkt->u.bind_ack.ctx_list[0].reason.value,
+ nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (pkt->u.bind_ack.num_results >= 2) {
+ if (pkt->u.bind_ack.ctx_list[1].result == DCERPC_BIND_ACK_RESULT_NEGOTIATE_ACK) {
+ conn->bind_time_features = pkt->u.bind_ack.ctx_list[1].reason.negotiate;
+ } else {
+ status = dcerpc_map_ack_reason(&pkt->u.bind_ack.ctx_list[1]);
+ DEBUG(10,("dcerpc: bind_time_feature failed - reason %d - %s\n",
+ pkt->u.bind_ack.ctx_list[1].reason.value,
+ nt_errstr(status)));
+ status = NT_STATUS_OK;
+ }
+ }
+
+ /*
+ * DCE-RPC 1.1 (c706) specifies
+ * CONST_MUST_RCV_FRAG_SIZE as 1432
+ */
+ if (pkt->u.bind_ack.max_xmit_frag < 1432) {
+ state->p->last_fault_code = DCERPC_NCA_S_PROTO_ERROR;
+ tevent_req_nterror(req, NT_STATUS_NET_WRITE_FAULT);
+ return;
+ }
+ if (pkt->u.bind_ack.max_recv_frag < 1432) {
+ state->p->last_fault_code = DCERPC_NCA_S_PROTO_ERROR;
+ tevent_req_nterror(req, NT_STATUS_NET_WRITE_FAULT);
+ return;
+ }
+ conn->srv_max_xmit_frag = MIN(conn->srv_max_xmit_frag,
+ pkt->u.bind_ack.max_xmit_frag);
+ conn->srv_max_recv_frag = MIN(conn->srv_max_recv_frag,
+ pkt->u.bind_ack.max_recv_frag);
+
+ flags = dcerpc_binding_get_flags(state->p->binding);
+
+ if (flags & DCERPC_CONCURRENT_MULTIPLEX) {
+ if (pkt->pfc_flags & DCERPC_PFC_FLAG_CONC_MPX) {
+ conn->flags |= DCERPC_CONCURRENT_MULTIPLEX;
+ } else {
+ conn->flags &= ~DCERPC_CONCURRENT_MULTIPLEX;
+ }
+ }
+
+ if (!(conn->flags & DCERPC_CONCURRENT_MULTIPLEX)) {
+ struct dcerpc_binding *pb =
+ discard_const_p(struct dcerpc_binding, state->p->binding);
+ /*
+ * clear DCERPC_CONCURRENT_MULTIPLEX
+ */
+ status = dcerpc_binding_set_flags(pb, 0,
+ DCERPC_CONCURRENT_MULTIPLEX);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ }
+ if ((conn->flags & DCERPC_PROPOSE_HEADER_SIGNING) &&
+ (pkt->pfc_flags & DCERPC_PFC_FLAG_SUPPORT_HEADER_SIGN)) {
+ conn->flags |= DCERPC_HEADER_SIGNING;
+ }
+
+ /* the bind_ack might contain a reply set of credentials */
+ if (pkt->auth_length != 0 && sec->tmp_auth_info.in != NULL) {
+ status = dcerpc_pull_auth_trailer(pkt, sec->tmp_auth_info.mem,
+ &pkt->u.bind_ack.auth_info,
+ sec->tmp_auth_info.in,
+ NULL, true);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ }
+
+ /*
+ * We're the owner of the binding, so we're allowed to modify it.
+ */
+ b = discard_const_p(struct dcerpc_binding, state->p->binding);
+ status = dcerpc_binding_set_assoc_group_id(b,
+ pkt->u.bind_ack.assoc_group_id);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS dcerpc_bind_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+/*
+ perform a continued bind (and auth3)
+*/
+NTSTATUS dcerpc_auth3(struct dcerpc_pipe *p,
+ TALLOC_CTX *mem_ctx)
+{
+ struct ncacn_packet pkt;
+ NTSTATUS status;
+ DATA_BLOB blob;
+ uint32_t flags;
+
+ flags = dcerpc_binding_get_flags(p->binding);
+
+ init_ncacn_hdr(p->conn, &pkt);
+
+ pkt.ptype = DCERPC_PKT_AUTH3;
+ pkt.pfc_flags = DCERPC_PFC_FLAG_FIRST | DCERPC_PFC_FLAG_LAST;
+ pkt.call_id = next_call_id(p->conn);
+ pkt.auth_length = 0;
+ pkt.u.auth3.auth_info = data_blob(NULL, 0);
+
+ if (flags & DCERPC_CONCURRENT_MULTIPLEX) {
+ pkt.pfc_flags |= DCERPC_PFC_FLAG_CONC_MPX;
+ }
+
+ /* construct the NDR form of the packet */
+ status = dcerpc_ncacn_push_auth(&blob,
+ mem_ctx,
+ &pkt,
+ p->conn->security_state.tmp_auth_info.out);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* send it on its way */
+ status = dcerpc_send_request(p->conn, &blob, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ process a fragment received from the transport layer during a
+ request
+
+ This function frees the data
+*/
+static void dcerpc_request_recv_data(struct dcecli_connection *c,
+ DATA_BLOB *raw_packet, struct ncacn_packet *pkt)
+{
+ struct rpc_request *req;
+ unsigned int length;
+ NTSTATUS status = NT_STATUS_OK;
+
+ /*
+ if this is an authenticated connection then parse and check
+ the auth info. We have to do this before finding the
+ matching packet, as the request structure might have been
+ removed due to a timeout, but if it has been we still need
+ to run the auth routines so that we don't get the sign/seal
+ info out of step with the server
+ */
+ switch (pkt->ptype) {
+ case DCERPC_PKT_RESPONSE:
+ status = ncacn_pull_pkt_auth(c, raw_packet->data,
+ DCERPC_PKT_RESPONSE,
+ 0, /* required_flags */
+ DCERPC_PFC_FLAG_FIRST |
+ DCERPC_PFC_FLAG_LAST,
+ DCERPC_REQUEST_LENGTH,
+ &pkt->u.response.stub_and_verifier,
+ raw_packet, pkt);
+ break;
+ default:
+ break;
+ }
+
+ /* find the matching request */
+ for (req=c->pending;req;req=req->next) {
+ if (pkt->call_id == req->call_id) break;
+ }
+
+#if 0
+ /* useful for testing certain vendors RPC servers */
+ if (req == NULL && c->pending && pkt->call_id == 0) {
+ DEBUG(0,("HACK FOR INCORRECT CALL ID\n"));
+ req = c->pending;
+ }
+#endif
+
+ if (req == NULL) {
+ DEBUG(2,("dcerpc_request: unmatched call_id %u in response packet\n", pkt->call_id));
+ data_blob_free(raw_packet);
+ return;
+ }
+
+ talloc_steal(req, raw_packet->data);
+
+ if (req->recv_handler != NULL) {
+ dcerpc_req_dequeue(req);
+ req->state = RPC_REQUEST_DONE;
+
+ /*
+ * We have to look at shipping further requests before calling
+ * the async function, that one might close the pipe
+ */
+ dcerpc_schedule_io_trigger(c);
+
+ req->recv_handler(req, raw_packet, pkt);
+ return;
+ }
+
+ if (pkt->ptype == DCERPC_PKT_FAULT) {
+ status = dcerpc_fault_to_nt_status(pkt->u.fault.status);
+ DEBUG(5,("rpc fault: %s\n", dcerpc_errstr(c, pkt->u.fault.status)));
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROTOCOL_ERROR)) {
+ dcerpc_connection_dead(c, status);
+ return;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) {
+ dcerpc_connection_dead(c, status);
+ return;
+ }
+ req->fault_code = pkt->u.fault.status;
+ req->status = NT_STATUS_NET_WRITE_FAULT;
+ goto req_done;
+ }
+
+ if (pkt->ptype != DCERPC_PKT_RESPONSE) {
+ DEBUG(2,("Unexpected packet type %d in dcerpc response\n",
+ (int)pkt->ptype));
+ dcerpc_connection_dead(c, NT_STATUS_RPC_PROTOCOL_ERROR);
+ return;
+ }
+
+ /* now check the status from the auth routines, and if it failed then fail
+ this request accordingly */
+ if (!NT_STATUS_IS_OK(status)) {
+ dcerpc_connection_dead(c, status);
+ return;
+ }
+
+ length = pkt->u.response.stub_and_verifier.length;
+
+ if (req->payload.length + length > c->max_total_response_size) {
+ DEBUG(2,("Unexpected total payload 0x%X > 0x%X dcerpc response\n",
+ (unsigned)req->payload.length + length,
+ (unsigned)c->max_total_response_size));
+ dcerpc_connection_dead(c, NT_STATUS_RPC_PROTOCOL_ERROR);
+ return;
+ }
+
+ if (length > 0) {
+ req->payload.data = talloc_realloc(req,
+ req->payload.data,
+ uint8_t,
+ req->payload.length + length);
+ if (!req->payload.data) {
+ req->status = NT_STATUS_NO_MEMORY;
+ goto req_done;
+ }
+ 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)) {
+ data_blob_free(raw_packet);
+ dcerpc_send_read(c);
+ return;
+ }
+
+ if (req->verify_bitmask1) {
+ req->p->conn->security_state.verified_bitmask1 = true;
+ }
+ if (req->verify_pcontext) {
+ req->p->verified_pcontext = true;
+ }
+
+ if (!(pkt->drep[0] & DCERPC_DREP_LE)) {
+ req->flags |= DCERPC_PULL_BIGENDIAN;
+ } else {
+ req->flags &= ~DCERPC_PULL_BIGENDIAN;
+ }
+
+req_done:
+ data_blob_free(raw_packet);
+
+ /* we've got the full payload */
+ dcerpc_req_dequeue(req);
+ req->state = RPC_REQUEST_DONE;
+
+ /*
+ * We have to look at shipping further requests before calling
+ * the async function, that one might close the pipe
+ */
+ dcerpc_schedule_io_trigger(c);
+
+ if (req->async.callback) {
+ req->async.callback(req);
+ }
+}
+
+static NTSTATUS dcerpc_request_prepare_vt(struct rpc_request *req);
+
+/*
+ perform the send side of a async dcerpc request
+*/
+static struct rpc_request *dcerpc_request_send(TALLOC_CTX *mem_ctx,
+ struct dcerpc_pipe *p,
+ const struct GUID *object,
+ uint16_t opnum,
+ DATA_BLOB *stub_data)
+{
+ struct rpc_request *req;
+ NTSTATUS status;
+
+ req = talloc_zero(mem_ctx, struct rpc_request);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ req->p = p;
+ req->call_id = next_call_id(p->conn);
+ req->state = RPC_REQUEST_QUEUED;
+
+ if (object != NULL) {
+ req->object = (struct GUID *)talloc_memdup(req, (const void *)object, sizeof(*object));
+ if (req->object == NULL) {
+ talloc_free(req);
+ return NULL;
+ }
+ }
+
+ req->opnum = opnum;
+ req->request_data.length = stub_data->length;
+ req->request_data.data = stub_data->data;
+
+ status = dcerpc_request_prepare_vt(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(req);
+ return NULL;
+ }
+
+ DLIST_ADD_END(p->conn->request_queue, req);
+ talloc_set_destructor(req, dcerpc_req_dequeue);
+
+ dcerpc_schedule_io_trigger(p->conn);
+
+ if (p->request_timeout) {
+ tevent_add_timer(p->conn->event_ctx, req,
+ timeval_current_ofs(p->request_timeout, 0),
+ dcerpc_timeout_handler, req);
+ }
+
+ return req;
+}
+
+static NTSTATUS dcerpc_request_prepare_vt(struct rpc_request *req)
+{
+ struct dcecli_security *sec = &req->p->conn->security_state;
+ struct dcerpc_sec_verification_trailer *t;
+ struct dcerpc_sec_vt *c = NULL;
+ struct ndr_push *ndr = NULL;
+ enum ndr_err_code ndr_err;
+
+ if (sec->auth_level < DCERPC_AUTH_LEVEL_PACKET) {
+ return NT_STATUS_OK;
+ }
+
+ t = talloc_zero(req, struct dcerpc_sec_verification_trailer);
+ if (t == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!sec->verified_bitmask1) {
+ t->commands = talloc_realloc(t, t->commands,
+ struct dcerpc_sec_vt,
+ t->count.count + 1);
+ if (t->commands == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ c = &t->commands[t->count.count++];
+ ZERO_STRUCTP(c);
+
+ c->command = DCERPC_SEC_VT_COMMAND_BITMASK1;
+ if (req->p->conn->flags & DCERPC_PROPOSE_HEADER_SIGNING) {
+ c->u.bitmask1 = DCERPC_SEC_VT_CLIENT_SUPPORTS_HEADER_SIGNING;
+ }
+ req->verify_bitmask1 = true;
+ }
+
+ if (!req->p->verified_pcontext) {
+ t->commands = talloc_realloc(t, t->commands,
+ struct dcerpc_sec_vt,
+ t->count.count + 1);
+ if (t->commands == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ c = &t->commands[t->count.count++];
+ ZERO_STRUCTP(c);
+
+ c->command = DCERPC_SEC_VT_COMMAND_PCONTEXT;
+ c->u.pcontext.abstract_syntax = req->p->syntax;
+ c->u.pcontext.transfer_syntax = req->p->transfer_syntax;
+
+ req->verify_pcontext = true;
+ }
+
+ if (!(req->p->conn->flags & DCERPC_HEADER_SIGNING)) {
+ t->commands = talloc_realloc(t, t->commands,
+ struct dcerpc_sec_vt,
+ t->count.count + 1);
+ if (t->commands == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ c = &t->commands[t->count.count++];
+ ZERO_STRUCTP(c);
+
+ c->command = DCERPC_SEC_VT_COMMAND_HEADER2;
+ c->u.header2.ptype = DCERPC_PKT_REQUEST;
+ if (req->p->conn->flags & DCERPC_PUSH_BIGENDIAN) {
+ c->u.header2.drep[0] = 0;
+ } else {
+ c->u.header2.drep[0] = DCERPC_DREP_LE;
+ }
+ c->u.header2.drep[1] = 0;
+ c->u.header2.drep[2] = 0;
+ c->u.header2.drep[3] = 0;
+ c->u.header2.call_id = req->call_id;
+ c->u.header2.context_id = req->p->context_id;
+ c->u.header2.opnum = req->opnum;
+ }
+
+ if (t->count.count == 0) {
+ TALLOC_FREE(t);
+ return NT_STATUS_OK;
+ }
+
+ c = &t->commands[t->count.count - 1];
+ c->command |= DCERPC_SEC_VT_COMMAND_END;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(dcerpc_sec_verification_trailer, t);
+ }
+
+ ndr = ndr_push_init_ctx(req);
+ if (ndr == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * for now we just copy and append
+ */
+
+ ndr_err = ndr_push_bytes(ndr, req->request_data.data,
+ req->request_data.length);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ ndr_err = ndr_push_dcerpc_sec_verification_trailer(ndr,
+ NDR_SCALARS | NDR_BUFFERS,
+ t);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+ req->request_data = ndr_push_blob(ndr);
+
+ return NT_STATUS_OK;
+}
+
+/*
+ Send a request using the transport
+*/
+
+static void dcerpc_ship_next_request(struct dcecli_connection *c)
+{
+ struct rpc_request *req;
+ struct dcerpc_pipe *p;
+ DATA_BLOB *stub_data;
+ struct ncacn_packet pkt;
+ DATA_BLOB blob;
+ uint32_t remaining, chunk_size;
+ bool first_packet = true;
+ size_t sig_size = 0;
+ bool need_async = false;
+ bool can_async = true;
+
+ req = c->request_queue;
+ if (req == NULL) {
+ return;
+ }
+
+ p = req->p;
+ stub_data = &req->request_data;
+
+ if (c->pending) {
+ need_async = true;
+ }
+
+ if (c->security_state.auth_level >= DCERPC_AUTH_LEVEL_PACKET) {
+ can_async = gensec_have_feature(c->security_state.generic_state,
+ GENSEC_FEATURE_ASYNC_REPLIES);
+ }
+
+ if (need_async && !can_async) {
+ req->wait_for_sync = true;
+ return;
+ }
+
+ DLIST_REMOVE(c->request_queue, req);
+ DLIST_ADD(c->pending, req);
+ req->state = RPC_REQUEST_PENDING;
+
+ init_ncacn_hdr(p->conn, &pkt);
+
+ remaining = stub_data->length;
+
+ /* we can write a full max_recv_frag size, minus the dcerpc
+ request header size */
+ chunk_size = p->conn->srv_max_recv_frag;
+ chunk_size -= DCERPC_REQUEST_LENGTH;
+ if (c->security_state.auth_level >= DCERPC_AUTH_LEVEL_PACKET) {
+ size_t max_payload = chunk_size;
+
+ max_payload -= DCERPC_AUTH_TRAILER_LENGTH;
+ max_payload -= (max_payload % DCERPC_AUTH_PAD_ALIGNMENT);
+
+ sig_size = gensec_sig_size(c->security_state.generic_state,
+ max_payload);
+ if (sig_size) {
+ chunk_size -= DCERPC_AUTH_TRAILER_LENGTH;
+ chunk_size -= sig_size;
+ }
+ }
+ chunk_size -= (chunk_size % DCERPC_AUTH_PAD_ALIGNMENT);
+
+ pkt.ptype = DCERPC_PKT_REQUEST;
+ pkt.call_id = req->call_id;
+ pkt.auth_length = 0;
+ pkt.pfc_flags = 0;
+ pkt.u.request.context_id = p->context_id;
+ pkt.u.request.opnum = req->opnum;
+
+ if (req->object) {
+ pkt.u.request.object.object = *req->object;
+ pkt.pfc_flags |= DCERPC_PFC_FLAG_OBJECT_UUID;
+ chunk_size -= ndr_size_GUID(req->object,0);
+ }
+
+ /* we send a series of pdus without waiting for a reply */
+ while (remaining > 0 || first_packet) {
+ uint32_t chunk = MIN(chunk_size, remaining);
+ bool last_frag = false;
+ bool do_trans = false;
+
+ first_packet = false;
+ pkt.pfc_flags &= ~(DCERPC_PFC_FLAG_FIRST |DCERPC_PFC_FLAG_LAST);
+
+ if (remaining == stub_data->length) {
+ pkt.pfc_flags |= DCERPC_PFC_FLAG_FIRST;
+ }
+ if (chunk == remaining) {
+ pkt.pfc_flags |= DCERPC_PFC_FLAG_LAST;
+ last_frag = true;
+ }
+
+ pkt.u.request.alloc_hint = remaining;
+ pkt.u.request.stub_and_verifier.data = stub_data->data +
+ (stub_data->length - remaining);
+ pkt.u.request.stub_and_verifier.length = chunk;
+
+ req->status = ncacn_push_request_sign(p->conn, &blob, req, sig_size, &pkt);
+ if (!NT_STATUS_IS_OK(req->status)) {
+ req->state = RPC_REQUEST_DONE;
+ DLIST_REMOVE(p->conn->pending, req);
+ return;
+ }
+
+ if (last_frag && !need_async) {
+ do_trans = true;
+ }
+
+ req->status = dcerpc_send_request(p->conn, &blob, do_trans);
+ if (!NT_STATUS_IS_OK(req->status)) {
+ req->state = RPC_REQUEST_DONE;
+ DLIST_REMOVE(p->conn->pending, req);
+ return;
+ }
+
+ if (last_frag && !do_trans) {
+ req->status = dcerpc_send_read(p->conn);
+ if (!NT_STATUS_IS_OK(req->status)) {
+ req->state = RPC_REQUEST_DONE;
+ DLIST_REMOVE(p->conn->pending, req);
+ return;
+ }
+ }
+
+ remaining -= chunk;
+ }
+}
+
+static void dcerpc_io_trigger(struct tevent_context *ctx,
+ struct tevent_immediate *im,
+ void *private_data)
+{
+ struct dcecli_connection *c =
+ talloc_get_type_abort(private_data,
+ struct dcecli_connection);
+
+ c->io_trigger_pending = false;
+
+ dcerpc_schedule_io_trigger(c);
+
+ dcerpc_ship_next_request(c);
+}
+
+static void dcerpc_schedule_io_trigger(struct dcecli_connection *c)
+{
+ if (c->dead) {
+ return;
+ }
+
+ if (c->request_queue == NULL) {
+ return;
+ }
+
+ if (c->request_queue->wait_for_sync && c->pending) {
+ return;
+ }
+
+ if (c->io_trigger_pending) {
+ return;
+ }
+
+ c->io_trigger_pending = true;
+
+ tevent_schedule_immediate(c->io_trigger,
+ c->event_ctx,
+ dcerpc_io_trigger,
+ c);
+}
+
+/*
+ perform the receive side of a async dcerpc request
+*/
+static NTSTATUS dcerpc_request_recv(struct rpc_request *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *stub_data)
+{
+ NTSTATUS status;
+
+ while (req->state != RPC_REQUEST_DONE) {
+ struct tevent_context *ctx = req->p->conn->event_ctx;
+ if (tevent_loop_once(ctx) != 0) {
+ return NT_STATUS_CONNECTION_DISCONNECTED;
+ }
+ }
+ *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_unlink(talloc_parent(req), req);
+ return status;
+}
+
+/*
+ this is a paranoid NDR validator. For every packet we push onto the wire
+ we pull it back again, then push it again. Then we compare the raw NDR data
+ for that to the NDR we initially generated. If they don't match then we know
+ we must have a bug in either the pull or push side of our code
+*/
+static NTSTATUS dcerpc_ndr_validate_in(struct dcecli_connection *c,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB blob,
+ size_t struct_size,
+ ndr_push_flags_fn_t ndr_push,
+ ndr_pull_flags_fn_t ndr_pull)
+{
+ void *st;
+ struct ndr_pull *pull;
+ struct ndr_push *push;
+ DATA_BLOB blob2;
+ enum ndr_err_code ndr_err;
+
+ st = talloc_size(mem_ctx, struct_size);
+ if (!st) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ pull = ndr_pull_init_flags(c, &blob, mem_ctx);
+ if (!pull) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ pull->flags |= LIBNDR_FLAG_REF_ALLOC;
+
+ if (c->flags & DCERPC_PUSH_BIGENDIAN) {
+ pull->flags |= LIBNDR_FLAG_BIGENDIAN;
+ }
+
+ if (c->flags & DCERPC_NDR64) {
+ pull->flags |= LIBNDR_FLAG_NDR64;
+ }
+
+ ndr_err = ndr_pull(pull, NDR_IN, st);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ndr_err = ndr_pull_error(pull, NDR_ERR_VALIDATE,
+ "failed input validation pull - %s",
+ nt_errstr(status));
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ push = ndr_push_init_ctx(mem_ctx);
+ if (!push) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (c->flags & DCERPC_PUSH_BIGENDIAN) {
+ push->flags |= LIBNDR_FLAG_BIGENDIAN;
+ }
+
+ if (c->flags & DCERPC_NDR64) {
+ push->flags |= LIBNDR_FLAG_NDR64;
+ }
+
+ ndr_err = ndr_push(push, NDR_IN, st);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ndr_err = ndr_pull_error(pull, NDR_ERR_VALIDATE,
+ "failed input validation push - %s",
+ nt_errstr(status));
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ blob2 = ndr_push_blob(push);
+
+ if (data_blob_cmp(&blob, &blob2) != 0) {
+ DEBUG(3,("original:\n"));
+ dump_data(3, blob.data, blob.length);
+ DEBUG(3,("secondary:\n"));
+ dump_data(3, blob2.data, blob2.length);
+ ndr_err = ndr_pull_error(pull, NDR_ERR_VALIDATE,
+ "failed input validation blobs doesn't match");
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ this is a paranoid NDR input validator. For every packet we pull
+ from the wire we push it back again then pull and push it
+ again. Then we compare the raw NDR data for that to the NDR we
+ initially generated. If they don't match then we know we must have a
+ bug in either the pull or push side of our code
+*/
+static NTSTATUS dcerpc_ndr_validate_out(struct dcecli_connection *c,
+ struct ndr_pull *pull_in,
+ void *struct_ptr,
+ size_t struct_size,
+ ndr_push_flags_fn_t ndr_push,
+ ndr_pull_flags_fn_t ndr_pull,
+ ndr_print_function_t ndr_print)
+{
+ void *st;
+ struct ndr_pull *pull;
+ struct ndr_push *push;
+ DATA_BLOB blob, blob2;
+ TALLOC_CTX *mem_ctx = pull_in;
+ char *s1, *s2;
+ enum ndr_err_code ndr_err;
+
+ st = talloc_size(mem_ctx, struct_size);
+ if (!st) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ memcpy(st, struct_ptr, struct_size);
+
+ push = ndr_push_init_ctx(mem_ctx);
+ if (!push) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ndr_err = ndr_push(push, NDR_OUT, struct_ptr);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ndr_err = ndr_push_error(push, NDR_ERR_VALIDATE,
+ "failed output validation push - %s",
+ nt_errstr(status));
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ blob = ndr_push_blob(push);
+
+ pull = ndr_pull_init_flags(c, &blob, mem_ctx);
+ if (!pull) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ pull->flags |= LIBNDR_FLAG_REF_ALLOC;
+ ndr_err = ndr_pull(pull, NDR_OUT, st);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ndr_err = ndr_pull_error(pull, NDR_ERR_VALIDATE,
+ "failed output validation pull - %s",
+ nt_errstr(status));
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ push = ndr_push_init_ctx(mem_ctx);
+ if (!push) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ndr_err = ndr_push(push, NDR_OUT, st);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ndr_err = ndr_push_error(push, NDR_ERR_VALIDATE,
+ "failed output validation push2 - %s",
+ nt_errstr(status));
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ blob2 = ndr_push_blob(push);
+
+ if (data_blob_cmp(&blob, &blob2) != 0) {
+ DEBUG(3,("original:\n"));
+ dump_data(3, blob.data, blob.length);
+ DEBUG(3,("secondary:\n"));
+ dump_data(3, blob2.data, blob2.length);
+ ndr_err = ndr_push_error(push, NDR_ERR_VALIDATE,
+ "failed output validation blobs doesn't match");
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ /* this checks the printed forms of the two structures, which effectively
+ tests all of the value() attributes */
+ s1 = ndr_print_function_string(mem_ctx, ndr_print, "VALIDATE",
+ NDR_OUT, struct_ptr);
+ s2 = ndr_print_function_string(mem_ctx, ndr_print, "VALIDATE",
+ NDR_OUT, st);
+ if (strcmp(s1, s2) != 0) {
+#if 1
+ DEBUG(3,("VALIDATE ERROR:\nWIRE:\n%s\n GEN:\n%s\n", s1, s2));
+#else
+ /* this is sometimes useful */
+ printf("VALIDATE ERROR\n");
+ file_save("wire.dat", s1, strlen(s1));
+ file_save("gen.dat", s2, strlen(s2));
+ system("diff -u wire.dat gen.dat");
+#endif
+ ndr_err = ndr_push_error(push, NDR_ERR_VALIDATE,
+ "failed output validation strings doesn't match");
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ a useful function for retrieving the server name we connected to
+*/
+_PUBLIC_ const char *dcerpc_server_name(struct dcerpc_pipe *p)
+{
+ return p->conn ? p->conn->server_name : NULL;
+}
+
+
+/*
+ get the dcerpc auth_level for a open connection
+*/
+uint32_t dcerpc_auth_level(struct dcecli_connection *c)
+{
+ uint8_t auth_level;
+
+ if (c->flags & DCERPC_SEAL) {
+ auth_level = DCERPC_AUTH_LEVEL_PRIVACY;
+ } else if (c->flags & DCERPC_SIGN) {
+ auth_level = DCERPC_AUTH_LEVEL_INTEGRITY;
+ } else if (c->flags & DCERPC_PACKET) {
+ auth_level = DCERPC_AUTH_LEVEL_PACKET;
+ } else if (c->flags & DCERPC_CONNECT) {
+ auth_level = DCERPC_AUTH_LEVEL_CONNECT;
+ } else {
+ auth_level = DCERPC_AUTH_LEVEL_NONE;
+ }
+ return auth_level;
+}
+
+struct dcerpc_alter_context_state {
+ struct tevent_context *ev;
+ struct dcerpc_pipe *p;
+};
+
+static void dcerpc_alter_context_fail_handler(struct rpc_request *subreq);
+static void dcerpc_alter_context_recv_handler(struct rpc_request *req,
+ DATA_BLOB *raw_packet,
+ struct ncacn_packet *pkt);
+
+struct tevent_req *dcerpc_alter_context_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct dcerpc_pipe *p,
+ const struct ndr_syntax_id *syntax,
+ const struct ndr_syntax_id *transfer_syntax)
+{
+ struct tevent_req *req;
+ struct dcerpc_alter_context_state *state;
+ struct ncacn_packet pkt;
+ DATA_BLOB blob;
+ NTSTATUS status;
+ struct rpc_request *subreq;
+ uint32_t flags;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct dcerpc_alter_context_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->p = p;
+
+ p->syntax = *syntax;
+ p->transfer_syntax = *transfer_syntax;
+
+ flags = dcerpc_binding_get_flags(p->binding);
+
+ init_ncacn_hdr(p->conn, &pkt);
+
+ pkt.ptype = DCERPC_PKT_ALTER;
+ pkt.pfc_flags = DCERPC_PFC_FLAG_FIRST | DCERPC_PFC_FLAG_LAST;
+ pkt.call_id = p->conn->call_id;
+ pkt.auth_length = 0;
+
+ if (flags & DCERPC_CONCURRENT_MULTIPLEX) {
+ pkt.pfc_flags |= DCERPC_PFC_FLAG_CONC_MPX;
+ }
+
+ pkt.u.alter.max_xmit_frag = p->conn->srv_max_xmit_frag;
+ pkt.u.alter.max_recv_frag = p->conn->srv_max_recv_frag;
+ pkt.u.alter.assoc_group_id = dcerpc_binding_get_assoc_group_id(p->binding);
+ pkt.u.alter.num_contexts = 1;
+ pkt.u.alter.ctx_list = talloc_zero_array(state, struct dcerpc_ctx_list,
+ pkt.u.alter.num_contexts);
+ if (tevent_req_nomem(pkt.u.alter.ctx_list, req)) {
+ return tevent_req_post(req, ev);
+ }
+ pkt.u.alter.ctx_list[0].context_id = p->context_id;
+ pkt.u.alter.ctx_list[0].num_transfer_syntaxes = 1;
+ pkt.u.alter.ctx_list[0].abstract_syntax = p->syntax;
+ pkt.u.alter.ctx_list[0].transfer_syntaxes = &p->transfer_syntax;
+ pkt.u.alter.auth_info = data_blob(NULL, 0);
+
+ /* construct the NDR form of the packet */
+ status = dcerpc_ncacn_push_auth(&blob,
+ state,
+ &pkt,
+ p->conn->security_state.tmp_auth_info.out);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * we allocate a dcerpc_request so we can be in the same
+ * request queue as normal requests
+ */
+ subreq = talloc_zero(state, struct rpc_request);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq->state = RPC_REQUEST_PENDING;
+ subreq->call_id = pkt.call_id;
+ subreq->async.private_data = req;
+ subreq->async.callback = dcerpc_alter_context_fail_handler;
+ subreq->p = p;
+ subreq->recv_handler = dcerpc_alter_context_recv_handler;
+ DLIST_ADD_END(p->conn->pending, subreq);
+ talloc_set_destructor(subreq, dcerpc_req_dequeue);
+
+ status = dcerpc_send_request(p->conn, &blob, true);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_add_timer(ev, subreq,
+ timeval_current_ofs(DCERPC_REQUEST_TIMEOUT, 0),
+ dcerpc_timeout_handler, subreq);
+
+ return req;
+}
+
+static void dcerpc_alter_context_fail_handler(struct rpc_request *subreq)
+{
+ struct tevent_req *req =
+ talloc_get_type_abort(subreq->async.private_data,
+ struct tevent_req);
+ struct dcerpc_alter_context_state *state =
+ tevent_req_data(req,
+ struct dcerpc_alter_context_state);
+ NTSTATUS status = subreq->status;
+
+ TALLOC_FREE(subreq);
+
+ /*
+ * We trigger the callback in the next event run
+ * because the code in this file might trigger
+ * multiple request callbacks from within a single
+ * while loop.
+ *
+ * In order to avoid segfaults from within
+ * dcerpc_connection_dead() we call
+ * tevent_req_defer_callback().
+ */
+ tevent_req_defer_callback(req, state->ev);
+
+ tevent_req_nterror(req, status);
+}
+
+static void dcerpc_alter_context_recv_handler(struct rpc_request *subreq,
+ DATA_BLOB *raw_packet,
+ struct ncacn_packet *pkt)
+{
+ struct tevent_req *req =
+ talloc_get_type_abort(subreq->async.private_data,
+ struct tevent_req);
+ struct dcerpc_alter_context_state *state =
+ tevent_req_data(req,
+ struct dcerpc_alter_context_state);
+ struct dcecli_connection *conn = state->p->conn;
+ struct dcecli_security *sec = &conn->security_state;
+ NTSTATUS status;
+
+ /*
+ * Note that pkt is allocated under raw_packet->data,
+ * while raw_packet->data is a child of subreq.
+ */
+ talloc_steal(state, raw_packet->data);
+ TALLOC_FREE(subreq);
+
+ /*
+ * We trigger the callback in the next event run
+ * because the code in this file might trigger
+ * multiple request callbacks from within a single
+ * while loop.
+ *
+ * In order to avoid segfaults from within
+ * dcerpc_connection_dead() we call
+ * tevent_req_defer_callback().
+ */
+ tevent_req_defer_callback(req, state->ev);
+
+ if (pkt->ptype == DCERPC_PKT_FAULT) {
+ DEBUG(5,("dcerpc: alter_resp - rpc fault: %s\n",
+ dcerpc_errstr(state, pkt->u.fault.status)));
+ if (pkt->u.fault.status == DCERPC_FAULT_ACCESS_DENIED) {
+ state->p->last_fault_code = pkt->u.fault.status;
+ tevent_req_nterror(req, NT_STATUS_LOGON_FAILURE);
+ } else if (pkt->u.fault.status == DCERPC_FAULT_SEC_PKG_ERROR) {
+ state->p->last_fault_code = pkt->u.fault.status;
+ tevent_req_nterror(req, NT_STATUS_LOGON_FAILURE);
+ } else {
+ state->p->last_fault_code = pkt->u.fault.status;
+ status = dcerpc_fault_to_nt_status(pkt->u.fault.status);
+ tevent_req_nterror(req, status);
+ }
+ return;
+ }
+
+ status = dcerpc_verify_ncacn_packet_header(pkt,
+ DCERPC_PKT_ALTER_RESP,
+ pkt->u.alter_resp.auth_info.length,
+ DCERPC_PFC_FLAG_FIRST |
+ DCERPC_PFC_FLAG_LAST,
+ DCERPC_PFC_FLAG_CONC_MPX |
+ DCERPC_PFC_FLAG_SUPPORT_HEADER_SIGN);
+ if (!NT_STATUS_IS_OK(status)) {
+ state->p->last_fault_code = DCERPC_NCA_S_PROTO_ERROR;
+ tevent_req_nterror(req, NT_STATUS_NET_WRITE_FAULT);
+ return;
+ }
+
+ if (pkt->u.alter_resp.num_results != 1) {
+ state->p->last_fault_code = DCERPC_NCA_S_PROTO_ERROR;
+ tevent_req_nterror(req, NT_STATUS_NET_WRITE_FAULT);
+ return;
+ }
+
+ if (pkt->u.alter_resp.ctx_list[0].result != 0) {
+ status = dcerpc_map_ack_reason(&pkt->u.alter_resp.ctx_list[0]);
+ DEBUG(2,("dcerpc: alter_resp failed - reason %d - %s\n",
+ pkt->u.alter_resp.ctx_list[0].reason.value,
+ nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ /* the alter_resp might contain a reply set of credentials */
+ if (pkt->auth_length != 0 && sec->tmp_auth_info.in != NULL) {
+ status = dcerpc_pull_auth_trailer(pkt, sec->tmp_auth_info.mem,
+ &pkt->u.alter_resp.auth_info,
+ sec->tmp_auth_info.in,
+ NULL, true);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS dcerpc_alter_context_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+/*
+ send a dcerpc alter_context request
+*/
+_PUBLIC_ NTSTATUS dcerpc_alter_context(struct dcerpc_pipe *p,
+ TALLOC_CTX *mem_ctx,
+ const struct ndr_syntax_id *syntax,
+ const struct ndr_syntax_id *transfer_syntax)
+{
+ struct tevent_req *subreq;
+ struct tevent_context *ev = p->conn->event_ctx;
+ bool ok;
+
+ /* TODO: create a new event context here */
+
+ subreq = dcerpc_alter_context_send(mem_ctx, ev,
+ p, syntax, transfer_syntax);
+ if (subreq == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ok = tevent_req_poll(subreq, ev);
+ if (!ok) {
+ NTSTATUS status;
+ status = map_nt_error_from_unix_common(errno);
+ return status;
+ }
+
+ return dcerpc_alter_context_recv(subreq);
+}
+
+static void dcerpc_transport_dead(struct dcecli_connection *c, NTSTATUS status)
+{
+ if (c->transport.stream == NULL) {
+ return;
+ }
+
+ tevent_queue_stop(c->transport.write_queue);
+ TALLOC_FREE(c->transport.read_subreq);
+ TALLOC_FREE(c->transport.stream);
+
+ if (NT_STATUS_EQUAL(NT_STATUS_UNSUCCESSFUL, status)) {
+ status = NT_STATUS_UNEXPECTED_NETWORK_ERROR;
+ }
+
+ if (NT_STATUS_EQUAL(NT_STATUS_OK, status)) {
+ status = NT_STATUS_END_OF_FILE;
+ }
+
+ dcerpc_recv_data(c, NULL, status);
+}
+
+
+/*
+ shutdown SMB pipe connection
+*/
+struct dcerpc_shutdown_pipe_state {
+ struct dcecli_connection *c;
+ NTSTATUS status;
+};
+
+static void dcerpc_shutdown_pipe_done(struct tevent_req *subreq);
+
+static NTSTATUS dcerpc_shutdown_pipe(struct dcecli_connection *c, NTSTATUS status)
+{
+ struct dcerpc_shutdown_pipe_state *state;
+ struct tevent_req *subreq;
+
+ if (c->transport.stream == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ state = talloc_zero(c, struct dcerpc_shutdown_pipe_state);
+ if (state == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ state->c = c;
+ state->status = status;
+
+ subreq = tstream_disconnect_send(state, c->event_ctx, c->transport.stream);
+ if (subreq == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq, dcerpc_shutdown_pipe_done, state);
+
+ return status;
+}
+
+static void dcerpc_shutdown_pipe_done(struct tevent_req *subreq)
+{
+ struct dcerpc_shutdown_pipe_state *state =
+ tevent_req_callback_data(subreq, struct dcerpc_shutdown_pipe_state);
+ struct dcecli_connection *c = state->c;
+ NTSTATUS status = state->status;
+ int error;
+
+ /*
+ * here we ignore the return values...
+ */
+ tstream_disconnect_recv(subreq, &error);
+ TALLOC_FREE(subreq);
+
+ TALLOC_FREE(state);
+
+ dcerpc_transport_dead(c, status);
+}
+
+
+
+struct dcerpc_send_read_state {
+ struct dcecli_connection *p;
+};
+
+static int dcerpc_send_read_state_destructor(struct dcerpc_send_read_state *state)
+{
+ struct dcecli_connection *p = state->p;
+
+ p->transport.read_subreq = NULL;
+
+ return 0;
+}
+
+static void dcerpc_send_read_done(struct tevent_req *subreq);
+
+static NTSTATUS dcerpc_send_read(struct dcecli_connection *p)
+{
+ struct dcerpc_send_read_state *state;
+
+ if (p->transport.read_subreq != NULL) {
+ p->transport.pending_reads++;
+ return NT_STATUS_OK;
+ }
+
+ state = talloc_zero(p, struct dcerpc_send_read_state);
+ if (state == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ state->p = p;
+
+ talloc_set_destructor(state, dcerpc_send_read_state_destructor);
+
+ p->transport.read_subreq = dcerpc_read_ncacn_packet_send(state,
+ p->event_ctx,
+ p->transport.stream);
+ if (p->transport.read_subreq == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(p->transport.read_subreq, dcerpc_send_read_done, state);
+
+ return NT_STATUS_OK;
+}
+
+static void dcerpc_send_read_done(struct tevent_req *subreq)
+{
+ struct dcerpc_send_read_state *state =
+ tevent_req_callback_data(subreq,
+ struct dcerpc_send_read_state);
+ struct dcecli_connection *p = state->p;
+ NTSTATUS status;
+ struct ncacn_packet *pkt;
+ DATA_BLOB blob;
+
+ status = dcerpc_read_ncacn_packet_recv(subreq, state,
+ &pkt, &blob);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(state);
+ dcerpc_transport_dead(p, status);
+ return;
+ }
+
+ /*
+ * here we steal into thet connection context,
+ * but p->transport.recv_data() will steal or free it again
+ */
+ talloc_steal(p, blob.data);
+ TALLOC_FREE(state);
+
+ if (p->transport.pending_reads > 0) {
+ p->transport.pending_reads--;
+
+ status = dcerpc_send_read(p);
+ if (!NT_STATUS_IS_OK(status)) {
+ dcerpc_transport_dead(p, status);
+ return;
+ }
+ }
+
+ dcerpc_recv_data(p, &blob, NT_STATUS_OK);
+}
+
+struct dcerpc_send_request_state {
+ struct dcecli_connection *p;
+ DATA_BLOB blob;
+ struct iovec iov;
+};
+
+static int dcerpc_send_request_state_destructor(struct dcerpc_send_request_state *state)
+{
+ struct dcecli_connection *p = state->p;
+
+ p->transport.read_subreq = NULL;
+
+ return 0;
+}
+
+static void dcerpc_send_request_wait_done(struct tevent_req *subreq);
+static void dcerpc_send_request_done(struct tevent_req *subreq);
+
+static NTSTATUS dcerpc_send_request(struct dcecli_connection *p, DATA_BLOB *data,
+ bool trigger_read)
+{
+ struct dcerpc_send_request_state *state;
+ struct tevent_req *subreq;
+ bool use_trans = trigger_read;
+
+ if (p->transport.stream == NULL) {
+ return NT_STATUS_CONNECTION_DISCONNECTED;
+ }
+
+ state = talloc_zero(p, struct dcerpc_send_request_state);
+ if (state == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ state->p = p;
+
+ state->blob = data_blob_talloc(state, data->data, data->length);
+ if (state->blob.data == NULL) {
+ TALLOC_FREE(state);
+ return NT_STATUS_NO_MEMORY;
+ }
+ state->iov.iov_base = (void *)state->blob.data;
+ state->iov.iov_len = state->blob.length;
+
+ if (p->transport.read_subreq != NULL) {
+ use_trans = false;
+ }
+
+ if (!tstream_is_smbXcli_np(p->transport.stream)) {
+ use_trans = false;
+ }
+
+ if (use_trans) {
+ /*
+ * we need to block reads until our write is
+ * the next in the write queue.
+ */
+ p->transport.read_subreq = tevent_queue_wait_send(state, p->event_ctx,
+ p->transport.write_queue);
+ if (p->transport.read_subreq == NULL) {
+ TALLOC_FREE(state);
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(p->transport.read_subreq,
+ dcerpc_send_request_wait_done,
+ state);
+
+ talloc_set_destructor(state, dcerpc_send_request_state_destructor);
+
+ trigger_read = false;
+ }
+
+ subreq = tstream_writev_queue_send(state, p->event_ctx,
+ p->transport.stream,
+ p->transport.write_queue,
+ &state->iov, 1);
+ if (subreq == NULL) {
+ TALLOC_FREE(state);
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq, dcerpc_send_request_done, state);
+
+ if (trigger_read) {
+ dcerpc_send_read(p);
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void dcerpc_send_request_wait_done(struct tevent_req *subreq)
+{
+ struct dcerpc_send_request_state *state =
+ tevent_req_callback_data(subreq,
+ struct dcerpc_send_request_state);
+ struct dcecli_connection *p = state->p;
+ NTSTATUS status;
+ bool ok;
+
+ p->transport.read_subreq = NULL;
+ talloc_set_destructor(state, NULL);
+
+ ok = tevent_queue_wait_recv(subreq);
+ if (!ok) {
+ TALLOC_FREE(state);
+ dcerpc_transport_dead(p, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ if (tevent_queue_length(p->transport.write_queue) <= 2) {
+ status = tstream_smbXcli_np_use_trans(p->transport.stream);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(state);
+ dcerpc_transport_dead(p, status);
+ return;
+ }
+ }
+
+ /* we free subreq after tstream_cli_np_use_trans */
+ TALLOC_FREE(subreq);
+
+ dcerpc_send_read(p);
+}
+
+static void dcerpc_send_request_done(struct tevent_req *subreq)
+{
+ struct dcerpc_send_request_state *state =
+ tevent_req_callback_data(subreq,
+ struct dcerpc_send_request_state);
+ int ret;
+ int error;
+
+ ret = tstream_writev_queue_recv(subreq, &error);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ struct dcecli_connection *p = state->p;
+ NTSTATUS status = map_nt_error_from_unix_common(error);
+
+ TALLOC_FREE(state);
+ dcerpc_transport_dead(p, status);
+ return;
+ }
+
+ TALLOC_FREE(state);
+}