diff options
Diffstat (limited to 'source4/librpc/rpc')
-rw-r--r-- | source4/librpc/rpc/dcerpc.c | 2630 | ||||
-rw-r--r-- | source4/librpc/rpc/dcerpc.h | 264 | ||||
-rw-r--r-- | source4/librpc/rpc/dcerpc.py | 18 | ||||
-rw-r--r-- | source4/librpc/rpc/dcerpc_auth.c | 556 | ||||
-rw-r--r-- | source4/librpc/rpc/dcerpc_connect.c | 1251 | ||||
-rw-r--r-- | source4/librpc/rpc/dcerpc_roh.c | 914 | ||||
-rw-r--r-- | source4/librpc/rpc/dcerpc_roh.h | 111 | ||||
-rw-r--r-- | source4/librpc/rpc/dcerpc_roh_channel_in.c | 303 | ||||
-rw-r--r-- | source4/librpc/rpc/dcerpc_roh_channel_out.c | 582 | ||||
-rw-r--r-- | source4/librpc/rpc/dcerpc_schannel.c | 620 | ||||
-rw-r--r-- | source4/librpc/rpc/dcerpc_secondary.c | 431 | ||||
-rw-r--r-- | source4/librpc/rpc/dcerpc_smb.c | 310 | ||||
-rw-r--r-- | source4/librpc/rpc/dcerpc_sock.c | 499 | ||||
-rw-r--r-- | source4/librpc/rpc/dcerpc_util.c | 811 | ||||
-rw-r--r-- | source4/librpc/rpc/pyrpc.c | 653 | ||||
-rw-r--r-- | source4/librpc/rpc/pyrpc.h | 60 | ||||
-rw-r--r-- | source4/librpc/rpc/pyrpc_util.c | 506 | ||||
-rw-r--r-- | source4/librpc/rpc/pyrpc_util.h | 70 |
18 files changed, 10589 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); +} diff --git a/source4/librpc/rpc/dcerpc.h b/source4/librpc/rpc/dcerpc.h new file mode 100644 index 0000000..57124f1 --- /dev/null +++ b/source4/librpc/rpc/dcerpc.h @@ -0,0 +1,264 @@ +/* + Unix SMB/CIFS implementation. + + DCERPC client side interface structures + + Copyright (C) Tim Potter 2003 + Copyright (C) Andrew Tridgell 2003-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/>. +*/ + +/* This is a public header file that is installed as part of Samba. + * If you remove any functions or change their signature, update + * the so version number. */ + +#ifndef __S4_DCERPC_H__ +#define __S4_DCERPC_H__ + +#include "../lib/util/data_blob.h" +#include "librpc/gen_ndr/dcerpc.h" +#include "../librpc/ndr/libndr.h" +#include "../librpc/rpc/rpc_common.h" + +struct tevent_context; +struct tevent_req; +struct dcerpc_binding_handle; +struct tstream_context; +struct ndr_interface_table; +struct resolve_context; + +/* + this defines a generic security context for signed/sealed dcerpc pipes. +*/ +struct dcecli_connection; +struct gensec_settings; +struct cli_credentials; +struct dcecli_security { + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + uint32_t auth_context_id; + struct { + struct dcerpc_auth *out; + struct dcerpc_auth *in; + TALLOC_CTX *mem; + } tmp_auth_info; + struct gensec_security *generic_state; + + /* get the session key */ + NTSTATUS (*session_key)(struct dcecli_connection *, DATA_BLOB *); + + bool verified_bitmask1; + +}; + +/* + this holds the information that is not specific to a particular rpc context_id +*/ +struct rpc_request; +struct dcecli_connection { + uint32_t call_id; + uint32_t srv_max_xmit_frag; + uint32_t srv_max_recv_frag; + uint32_t flags; + struct dcecli_security security_state; + struct tevent_context *event_ctx; + + struct tevent_immediate *io_trigger; + bool io_trigger_pending; + + /** Directory in which to save ndrdump-parseable files */ + const char *packet_log_dir; + + bool dead; + bool free_skipped; + + struct dcerpc_transport { + enum dcerpc_transport_t transport; + void *private_data; + bool encrypted; + + struct tstream_context *stream; + /** to serialize write events */ + struct tevent_queue *write_queue; + /** the current active read request if any */ + struct tevent_req *read_subreq; + /** number of read requests other than the current active */ + uint32_t pending_reads; + } transport; + + const char *server_name; + + /* Requests that have been sent, waiting for a reply */ + struct rpc_request *pending; + + /* Sync requests waiting to be shipped */ + struct rpc_request *request_queue; + + /* the next context_id to be assigned */ + uint32_t next_context_id; + + /* The maximum total payload of reassembled response pdus */ + size_t max_total_response_size; + + /* the negotiated bind time features */ + uint16_t bind_time_features; +}; + +/* + this encapsulates a full dcerpc client side pipe +*/ +struct dcerpc_pipe { + struct dcerpc_binding_handle *binding_handle; + + uint32_t context_id; + + struct GUID object; + struct ndr_syntax_id syntax; + struct ndr_syntax_id transfer_syntax; + + struct dcecli_connection *conn; + const struct dcerpc_binding *binding; + + /** the last fault code from a DCERPC fault */ + uint32_t last_fault_code; + + /** timeout for individual rpc requests, in seconds */ + uint32_t request_timeout; + + /* + * Set for the timeout in dcerpc_pipe_connect_b_send(), to + * allow the timeout not to destory the stack during a nested + * event loop caused by gensec_update() + */ + bool inhibit_timeout_processing; + bool timed_out; + + bool verified_pcontext; +}; + +/* default timeout for all rpc requests, in seconds */ +#define DCERPC_REQUEST_TIMEOUT 60 + +struct epm_tower; +struct epm_floor; + +struct smbcli_tree; +struct smb2_tree; +struct smbXcli_conn; +struct smbXcli_session; +struct smbXcli_tcon; +struct roh_connection; +struct tstream_tls_params; +struct socket_address; + +NTSTATUS dcerpc_pipe_connect(TALLOC_CTX *parent_ctx, + struct dcerpc_pipe **pp, + const char *binding, + const struct ndr_interface_table *table, + struct cli_credentials *credentials, + struct tevent_context *ev, + struct loadparm_context *lp_ctx); +const char *dcerpc_server_name(struct dcerpc_pipe *p); +struct dcerpc_pipe *dcerpc_pipe_init(TALLOC_CTX *mem_ctx, struct tevent_context *ev); +NTSTATUS dcerpc_pipe_open_smb(struct dcerpc_pipe *p, + struct smbcli_tree *tree, + const char *pipe_name); +NTSTATUS dcerpc_pipe_open_smb2(struct dcerpc_pipe *p, + struct smb2_tree *tree, + const char *pipe_name); +NTSTATUS dcerpc_bind_auth_none(struct dcerpc_pipe *p, + const struct ndr_interface_table *table); +NTSTATUS dcerpc_fetch_session_key(struct dcerpc_pipe *p, + DATA_BLOB *session_key); +bool dcerpc_transport_encrypted(struct dcerpc_pipe *p); +struct composite_context; +NTSTATUS dcerpc_secondary_connection_recv(struct composite_context *c, + struct dcerpc_pipe **p2); + +struct composite_context* dcerpc_pipe_connect_b_send(TALLOC_CTX *parent_ctx, + const struct dcerpc_binding *binding, + const struct ndr_interface_table *table, + struct cli_credentials *credentials, + struct tevent_context *ev, + struct loadparm_context *lp_ctx); + +NTSTATUS dcerpc_pipe_connect_b_recv(struct composite_context *c, TALLOC_CTX *mem_ctx, + struct dcerpc_pipe **p); + +NTSTATUS dcerpc_pipe_connect_b(TALLOC_CTX *parent_ctx, + struct dcerpc_pipe **pp, + const struct dcerpc_binding *binding, + const struct ndr_interface_table *table, + struct cli_credentials *credentials, + struct tevent_context *ev, + struct loadparm_context *lp_ctx); + +NTSTATUS dcerpc_pipe_auth(TALLOC_CTX *mem_ctx, + struct dcerpc_pipe **p, + const struct dcerpc_binding *binding, + const struct ndr_interface_table *table, + struct cli_credentials *credentials, + struct loadparm_context *lp_ctx); +NTSTATUS dcerpc_init(void); +struct composite_context *dcerpc_secondary_smb_send(struct dcecli_connection *c1, + struct dcecli_connection *c2, + const char *pipe_name); +NTSTATUS dcerpc_secondary_smb_recv(struct composite_context *c); +NTSTATUS dcerpc_secondary_context(struct dcerpc_pipe *p, + struct dcerpc_pipe **pp2, + const struct ndr_interface_table *table); +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); + +NTSTATUS dcerpc_bind_auth(struct dcerpc_pipe *p, + const struct ndr_interface_table *table, + struct cli_credentials *credentials, + struct gensec_settings *gensec_settings, + uint8_t auth_type, uint8_t auth_level, + const char *service); +struct composite_context* dcerpc_pipe_connect_send(TALLOC_CTX *parent_ctx, + const char *binding, + const struct ndr_interface_table *table, + struct cli_credentials *credentials, + struct tevent_context *ev, struct loadparm_context *lp_ctx); +NTSTATUS dcerpc_pipe_connect_recv(struct composite_context *c, + TALLOC_CTX *mem_ctx, + struct dcerpc_pipe **pp); + +NTSTATUS dcerpc_epm_map_binding(TALLOC_CTX *mem_ctx, struct dcerpc_binding *binding, + const struct ndr_interface_table *table, struct tevent_context *ev, + struct loadparm_context *lp_ctx); +struct composite_context* dcerpc_secondary_auth_connection_send(struct dcerpc_pipe *p, + const struct dcerpc_binding *binding, + const struct ndr_interface_table *table, + struct cli_credentials *credentials, + struct loadparm_context *lp_ctx); +NTSTATUS dcerpc_secondary_auth_connection_recv(struct composite_context *c, + TALLOC_CTX *mem_ctx, + struct dcerpc_pipe **p); +NTSTATUS dcerpc_secondary_auth_connection(struct dcerpc_pipe *p, + const struct dcerpc_binding *binding, + const struct ndr_interface_table *table, + struct cli_credentials *credentials, + struct loadparm_context *lp_ctx, + TALLOC_CTX *mem_ctx, + struct dcerpc_pipe **p2); + +struct composite_context* dcerpc_secondary_connection_send(struct dcerpc_pipe *p, + const struct dcerpc_binding *b); + +#endif /* __S4_DCERPC_H__ */ diff --git a/source4/librpc/rpc/dcerpc.py b/source4/librpc/rpc/dcerpc.py new file mode 100644 index 0000000..64dd6e3 --- /dev/null +++ b/source4/librpc/rpc/dcerpc.py @@ -0,0 +1,18 @@ +# Unix SMB/CIFS implementation. +# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2008 +# +# 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/>. +# + +from samba.dcerpc.base import * diff --git a/source4/librpc/rpc/dcerpc_auth.c b/source4/librpc/rpc/dcerpc_auth.c new file mode 100644 index 0000000..52dffec --- /dev/null +++ b/source4/librpc/rpc/dcerpc_auth.c @@ -0,0 +1,556 @@ +/* + Unix SMB/CIFS implementation. + + Generic Authentication Interface + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + Copyright (C) Stefan Metzmacher 2004 + + 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 <tevent.h> +#include "libcli/composite/composite.h" +#include "auth/gensec/gensec.h" +#include "librpc/rpc/dcerpc.h" +#include "librpc/rpc/dcerpc_proto.h" +#include "param/param.h" + +/* + return the rpc syntax and transfer syntax given the pipe uuid and version +*/ +static NTSTATUS dcerpc_init_syntaxes(struct dcerpc_pipe *p, + const struct ndr_interface_table *table, + struct ndr_syntax_id *syntax, + struct ndr_syntax_id *transfer_syntax) +{ + struct GUID *object = NULL; + + p->object = dcerpc_binding_get_object(p->binding); + if (!GUID_all_zero(&p->object)) { + object = &p->object; + } + + p->binding_handle = dcerpc_pipe_binding_handle(p, object, table); + if (p->binding_handle == NULL) { + return NT_STATUS_NO_MEMORY; + } + + syntax->uuid = table->syntax_id.uuid; + syntax->if_version = table->syntax_id.if_version; + + if (p->conn->flags & DCERPC_NDR64) { + *transfer_syntax = ndr_transfer_syntax_ndr64; + } else { + *transfer_syntax = ndr_transfer_syntax_ndr; + } + + return NT_STATUS_OK; +} + + +/* + Send request to do a non-authenticated dcerpc bind +*/ +static void dcerpc_bind_auth_none_done(struct tevent_req *subreq); + +struct composite_context *dcerpc_bind_auth_none_send(TALLOC_CTX *mem_ctx, + struct dcerpc_pipe *p, + const struct ndr_interface_table *table) +{ + struct ndr_syntax_id syntax; + struct ndr_syntax_id transfer_syntax; + + struct composite_context *c; + struct tevent_req *subreq; + + c = composite_create(mem_ctx, p->conn->event_ctx); + if (c == NULL) return NULL; + + c->status = dcerpc_init_syntaxes(p, table, + &syntax, &transfer_syntax); + if (!NT_STATUS_IS_OK(c->status)) { + DEBUG(2,("Invalid uuid string in " + "dcerpc_bind_auth_none_send\n")); + composite_error(c, c->status); + return c; + } + + subreq = dcerpc_bind_send(mem_ctx, p->conn->event_ctx, p, + &syntax, &transfer_syntax); + if (composite_nomem(subreq, c)) return c; + tevent_req_set_callback(subreq, dcerpc_bind_auth_none_done, c); + + return c; +} + +static void dcerpc_bind_auth_none_done(struct tevent_req *subreq) +{ + struct composite_context *ctx = + tevent_req_callback_data(subreq, + struct composite_context); + + ctx->status = dcerpc_bind_recv(subreq); + TALLOC_FREE(subreq); + if (!composite_is_ok(ctx)) return; + + composite_done(ctx); +} + +/* + Receive result of a non-authenticated dcerpc bind +*/ +NTSTATUS dcerpc_bind_auth_none_recv(struct composite_context *ctx) +{ + NTSTATUS result = composite_wait(ctx); + TALLOC_FREE(ctx); + return result; +} + + +/* + Perform sync non-authenticated dcerpc bind +*/ +_PUBLIC_ NTSTATUS dcerpc_bind_auth_none(struct dcerpc_pipe *p, + const struct ndr_interface_table *table) +{ + struct composite_context *ctx; + + ctx = dcerpc_bind_auth_none_send(p, p, table); + return dcerpc_bind_auth_none_recv(ctx); +} + + +struct bind_auth_state { + struct dcerpc_pipe *pipe; + struct ndr_syntax_id syntax; + struct ndr_syntax_id transfer_syntax; + struct dcerpc_auth out_auth_info; + struct dcerpc_auth in_auth_info; + bool more_processing; /* Is there anything more to do after the + * first bind itself received? */ +}; + +static void bind_auth_next_gensec_done(struct tevent_req *subreq); +static void bind_auth_recv_alter(struct tevent_req *subreq); + +static void bind_auth_next_step(struct composite_context *c) +{ + struct bind_auth_state *state; + struct dcecli_security *sec; + struct tevent_req *subreq; + + state = talloc_get_type(c->private_data, struct bind_auth_state); + sec = &state->pipe->conn->security_state; + + if (state->in_auth_info.auth_type != sec->auth_type) { + composite_error(c, NT_STATUS_RPC_PROTOCOL_ERROR); + return; + } + + if (state->in_auth_info.auth_level != sec->auth_level) { + composite_error(c, NT_STATUS_RPC_PROTOCOL_ERROR); + return; + } + + if (state->in_auth_info.auth_context_id != sec->auth_context_id) { + composite_error(c, NT_STATUS_RPC_PROTOCOL_ERROR); + return; + } + + state->out_auth_info = (struct dcerpc_auth) { + .auth_type = sec->auth_type, + .auth_level = sec->auth_level, + .auth_context_id = sec->auth_context_id, + }; + + /* The status value here, from GENSEC is vital to the security + * of the system. Even if the other end accepts, if GENSEC + * claims 'MORE_PROCESSING_REQUIRED' then you must keep + * feeding it blobs, or else the remote host/attacker might + * avoid mutal authentication requirements. + * + * Likewise, you must not feed GENSEC too much (after the OK), + * it doesn't like that either + */ + + state->pipe->inhibit_timeout_processing = true; + state->pipe->timed_out = false; + + subreq = gensec_update_send(state, + state->pipe->conn->event_ctx, + sec->generic_state, + state->in_auth_info.credentials); + if (composite_nomem(subreq, c)) return; + tevent_req_set_callback(subreq, bind_auth_next_gensec_done, c); +} + +static void bind_auth_next_gensec_done(struct tevent_req *subreq) +{ + struct composite_context *c = + tevent_req_callback_data(subreq, + struct composite_context); + struct bind_auth_state *state = + talloc_get_type_abort(c->private_data, + struct bind_auth_state); + struct dcerpc_pipe *p = state->pipe; + struct dcecli_security *sec = &p->conn->security_state; + bool more_processing = false; + + state->pipe->inhibit_timeout_processing = false; + + c->status = gensec_update_recv(subreq, state, + &state->out_auth_info.credentials); + TALLOC_FREE(subreq); + + if (NT_STATUS_EQUAL(c->status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + more_processing = true; + c->status = NT_STATUS_OK; + } + + if (!composite_is_ok(c)) return; + + if (!more_processing) { + if (state->pipe->conn->flags & DCERPC_HEADER_SIGNING) { + gensec_want_feature(sec->generic_state, + GENSEC_FEATURE_SIGN_PKT_HEADER); + } + } + + if (state->out_auth_info.credentials.length == 0) { + composite_done(c); + return; + } + + state->in_auth_info = (struct dcerpc_auth) { + .auth_type = DCERPC_AUTH_TYPE_NONE, + }; + sec->tmp_auth_info.in = &state->in_auth_info; + sec->tmp_auth_info.mem = state; + sec->tmp_auth_info.out = &state->out_auth_info; + + if (!more_processing) { + /* NO reply expected, so just send it */ + c->status = dcerpc_auth3(state->pipe, state); + if (!composite_is_ok(c)) return; + + composite_done(c); + return; + } + + /* We are demanding a reply, so use a request that will get us one */ + + subreq = dcerpc_alter_context_send(state, state->pipe->conn->event_ctx, + state->pipe, + &state->pipe->syntax, + &state->pipe->transfer_syntax); + if (composite_nomem(subreq, c)) return; + tevent_req_set_callback(subreq, bind_auth_recv_alter, c); +} + + +static void bind_auth_recv_alter(struct tevent_req *subreq) +{ + struct composite_context *c = + tevent_req_callback_data(subreq, + struct composite_context); + struct bind_auth_state *state = talloc_get_type(c->private_data, + struct bind_auth_state); + struct dcecli_security *sec = &state->pipe->conn->security_state; + + ZERO_STRUCT(sec->tmp_auth_info); + + c->status = dcerpc_alter_context_recv(subreq); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + bind_auth_next_step(c); +} + + +static void bind_auth_recv_bindreply(struct tevent_req *subreq) +{ + struct composite_context *c = + tevent_req_callback_data(subreq, + struct composite_context); + struct bind_auth_state *state = talloc_get_type(c->private_data, + struct bind_auth_state); + struct dcecli_security *sec = &state->pipe->conn->security_state; + + ZERO_STRUCT(sec->tmp_auth_info); + + c->status = dcerpc_bind_recv(subreq); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + if (!state->more_processing) { + /* The first gensec_update has not requested a second run, so + * we're done here. */ + composite_done(c); + return; + } + + bind_auth_next_step(c); +} + + +static void dcerpc_bind_auth_gensec_done(struct tevent_req *subreq); + +/** + Bind to a DCE/RPC pipe, send async request + @param mem_ctx TALLOC_CTX for the allocation of the composite_context + @param p The dcerpc_pipe to bind (must already be connected) + @param table The interface table to use (the DCE/RPC bind both selects and interface and authenticates) + @param credentials The credentials of the account to connect with + @param auth_type Select the authentication scheme to use + @param auth_level Chooses between unprotected (connect), signed or sealed + @param service The service (used by Kerberos to select the service principal to contact) + @retval A composite context describing the partial state of the bind +*/ + +struct composite_context *dcerpc_bind_auth_send(TALLOC_CTX *mem_ctx, + struct dcerpc_pipe *p, + const struct ndr_interface_table *table, + struct cli_credentials *credentials, + struct gensec_settings *gensec_settings, + uint8_t auth_type, uint8_t auth_level, + const char *service) +{ + struct composite_context *c; + struct bind_auth_state *state; + struct dcecli_security *sec; + struct tevent_req *subreq; + const char *target_principal = NULL; + + /* composite context allocation and setup */ + c = composite_create(mem_ctx, p->conn->event_ctx); + if (c == NULL) return NULL; + + state = talloc(c, struct bind_auth_state); + if (composite_nomem(state, c)) return c; + c->private_data = state; + + state->pipe = p; + + c->status = dcerpc_init_syntaxes(p, table, + &state->syntax, + &state->transfer_syntax); + if (!composite_is_ok(c)) return c; + + sec = &p->conn->security_state; + + c->status = gensec_client_start(p, &sec->generic_state, + gensec_settings); + if (!NT_STATUS_IS_OK(c->status)) { + DEBUG(1, ("Failed to start GENSEC client mode: %s\n", + nt_errstr(c->status))); + composite_error(c, c->status); + return c; + } + + c->status = gensec_set_credentials(sec->generic_state, credentials); + if (!NT_STATUS_IS_OK(c->status)) { + DEBUG(1, ("Failed to set GENSEC client credentials: %s\n", + nt_errstr(c->status))); + composite_error(c, c->status); + return c; + } + + c->status = gensec_set_target_hostname(sec->generic_state, + dcerpc_server_name(p)); + if (!NT_STATUS_IS_OK(c->status)) { + DEBUG(1, ("Failed to set GENSEC target hostname: %s\n", + nt_errstr(c->status))); + composite_error(c, c->status); + return c; + } + + if (service != NULL) { + c->status = gensec_set_target_service(sec->generic_state, + service); + if (!NT_STATUS_IS_OK(c->status)) { + DEBUG(1, ("Failed to set GENSEC target service: %s\n", + nt_errstr(c->status))); + composite_error(c, c->status); + return c; + } + } + + if (p->binding != NULL) { + target_principal = dcerpc_binding_get_string_option(p->binding, + "target_principal"); + } + if (target_principal != NULL) { + c->status = gensec_set_target_principal(sec->generic_state, + target_principal); + if (!NT_STATUS_IS_OK(c->status)) { + DEBUG(1, ("Failed to set GENSEC target principal to %s: %s\n", + target_principal, nt_errstr(c->status))); + composite_error(c, c->status); + return c; + } + } + + c->status = gensec_start_mech_by_authtype(sec->generic_state, + auth_type, auth_level); + if (!NT_STATUS_IS_OK(c->status)) { + DEBUG(1, ("Failed to start GENSEC client mechanism %s: %s\n", + gensec_get_name_by_authtype(sec->generic_state, auth_type), + nt_errstr(c->status))); + composite_error(c, c->status); + return c; + } + + sec->auth_type = auth_type; + sec->auth_level = auth_level, + /* + * We use auth_context_id = 1 as some older + * Samba versions (<= 4.2.3) use that value hardcoded + * in a response. + */ + sec->auth_context_id = 1; + + state->out_auth_info = (struct dcerpc_auth) { + .auth_type = sec->auth_type, + .auth_level = sec->auth_level, + .auth_context_id = sec->auth_context_id, + }; + + /* The status value here, from GENSEC is vital to the security + * of the system. Even if the other end accepts, if GENSEC + * claims 'MORE_PROCESSING_REQUIRED' then you must keep + * feeding it blobs, or else the remote host/attacker might + * avoid mutal authentication requirements. + * + * Likewise, you must not feed GENSEC too much (after the OK), + * it doesn't like that either + */ + + state->pipe->inhibit_timeout_processing = true; + state->pipe->timed_out = false; + + subreq = gensec_update_send(state, + p->conn->event_ctx, + sec->generic_state, + data_blob_null); + if (composite_nomem(subreq, c)) return c; + tevent_req_set_callback(subreq, dcerpc_bind_auth_gensec_done, c); + + return c; +} + +static void dcerpc_bind_auth_gensec_done(struct tevent_req *subreq) +{ + struct composite_context *c = + tevent_req_callback_data(subreq, + struct composite_context); + struct bind_auth_state *state = + talloc_get_type_abort(c->private_data, + struct bind_auth_state); + struct dcerpc_pipe *p = state->pipe; + struct dcecli_security *sec = &p->conn->security_state; + + state->pipe->inhibit_timeout_processing = false; + + c->status = gensec_update_recv(subreq, state, + &state->out_auth_info.credentials); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(c->status) && + !NT_STATUS_EQUAL(c->status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + composite_error(c, c->status); + return; + } + + state->more_processing = NT_STATUS_EQUAL(c->status, + NT_STATUS_MORE_PROCESSING_REQUIRED); + + if (state->out_auth_info.credentials.length == 0) { + composite_done(c); + return; + } + + if (gensec_have_feature(sec->generic_state, GENSEC_FEATURE_SIGN_PKT_HEADER)) { + if (sec->auth_level >= DCERPC_AUTH_LEVEL_PACKET) { + state->pipe->conn->flags |= DCERPC_PROPOSE_HEADER_SIGNING; + } + } + + state->in_auth_info = (struct dcerpc_auth) { + .auth_type = DCERPC_AUTH_TYPE_NONE, + }; + sec->tmp_auth_info.in = &state->in_auth_info; + sec->tmp_auth_info.mem = state; + sec->tmp_auth_info.out = &state->out_auth_info; + + /* The first request always is a dcerpc_bind. The subsequent ones + * depend on gensec results */ + subreq = dcerpc_bind_send(state, p->conn->event_ctx, p, + &state->syntax, &state->transfer_syntax); + if (composite_nomem(subreq, c)) return; + tevent_req_set_callback(subreq, bind_auth_recv_bindreply, c); + + return; +} + + +/** + Bind to a DCE/RPC pipe, receive result + @param creq A composite context describing state of async call + @retval NTSTATUS code +*/ + +NTSTATUS dcerpc_bind_auth_recv(struct composite_context *creq) +{ + NTSTATUS result = composite_wait(creq); + struct bind_auth_state *state = talloc_get_type(creq->private_data, + struct bind_auth_state); + + if (NT_STATUS_IS_OK(result)) { + /* + after a successful authenticated bind the session + key reverts to the generic session key + */ + state->pipe->conn->security_state.session_key = dcecli_generic_session_key; + } + + talloc_free(creq); + return result; +} + + +/** + Perform a GENSEC authenticated bind to a DCE/RPC pipe, sync + @param p The dcerpc_pipe to bind (must already be connected) + @param table The interface table to use (the DCE/RPC bind both selects and interface and authenticates) + @param credentials The credentials of the account to connect with + @param auth_type Select the authentication scheme to use + @param auth_level Chooses between unprotected (connect), signed or sealed + @param service The service (used by Kerberos to select the service principal to contact) + @retval NTSTATUS status code +*/ + +_PUBLIC_ NTSTATUS dcerpc_bind_auth(struct dcerpc_pipe *p, + const struct ndr_interface_table *table, + struct cli_credentials *credentials, + struct gensec_settings *gensec_settings, + uint8_t auth_type, uint8_t auth_level, + const char *service) +{ + struct composite_context *creq; + creq = dcerpc_bind_auth_send(p, p, table, credentials, gensec_settings, + auth_type, auth_level, service); + return dcerpc_bind_auth_recv(creq); +} diff --git a/source4/librpc/rpc/dcerpc_connect.c b/source4/librpc/rpc/dcerpc_connect.c new file mode 100644 index 0000000..9a7b9ae --- /dev/null +++ b/source4/librpc/rpc/dcerpc_connect.c @@ -0,0 +1,1251 @@ +/* + Unix SMB/CIFS implementation. + + dcerpc connect functions + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Jelmer Vernooij 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2007 + Copyright (C) Rafal Szczesniak 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 "libcli/composite/composite.h" +#include "libcli/smb_composite/smb_composite.h" +#include "lib/events/events.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "libcli/smb/smbXcli_base.h" +#include "librpc/rpc/dcerpc.h" +#include "librpc/rpc/dcerpc_proto.h" +#include "auth/credentials/credentials.h" +#include "param/param.h" +#include "libcli/resolve/resolve.h" +#include "libcli/http/http.h" +#include "lib/util/util_net.h" + +#undef strcasecmp + +struct dcerpc_pipe_connect { + struct dcecli_connection *conn; + struct dcerpc_binding *binding; + const struct ndr_interface_table *interface; + struct cli_credentials *creds; + struct resolve_context *resolve_ctx; + struct { + const char *dir; + } ncalrpc; + struct { + struct smbXcli_conn *conn; + struct smbXcli_session *session; + struct smbXcli_tcon *tcon; + const char *pipe_name; + } smb; +}; + +struct pipe_np_smb_state { + struct smb_composite_connect conn; + struct dcerpc_pipe_connect io; +}; + + +/* + Stage 3 of ncacn_np_smb: Named pipe opened (or not) +*/ +static void continue_pipe_open_smb(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + + /* receive result of named pipe open request on smb */ + c->status = dcerpc_pipe_open_smb_recv(ctx); + if (!composite_is_ok(c)) return; + + composite_done(c); +} + +static void continue_smb_open(struct composite_context *c); +static void continue_smb2_connect(struct tevent_req *subreq); +static void continue_smbXcli_connect(struct tevent_req *subreq); + +/* + Stage 2 of ncacn_np_smb: Open a named pipe after successful smb connection +*/ +static void continue_smb_connect(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + struct pipe_np_smb_state *s = talloc_get_type(c->private_data, + struct pipe_np_smb_state); + struct smbcli_tree *t; + + /* receive result of smb connect request */ + c->status = smb_composite_connect_recv(ctx, s->io.conn); + if (!composite_is_ok(c)) return; + + t = s->conn.out.tree; + + /* prepare named pipe open parameters */ + s->io.smb.conn = t->session->transport->conn; + s->io.smb.session = t->session->smbXcli; + s->io.smb.tcon = t->smbXcli; + smb1cli_tcon_set_id(s->io.smb.tcon, t->tid); + s->io.smb.pipe_name = dcerpc_binding_get_string_option(s->io.binding, + "endpoint"); + + continue_smb_open(c); +} + +static void continue_smb_open(struct composite_context *c) +{ + struct pipe_np_smb_state *s = talloc_get_type(c->private_data, + struct pipe_np_smb_state); + struct composite_context *open_ctx; + + /* send named pipe open request */ + open_ctx = dcerpc_pipe_open_smb_send(s->io.conn, + s->io.smb.conn, + s->io.smb.session, + s->io.smb.tcon, + DCERPC_REQUEST_TIMEOUT * 1000, + s->io.smb.pipe_name); + if (composite_nomem(open_ctx, c)) return; + + composite_continue(c, open_ctx, continue_pipe_open_smb, c); +} + + +/* + Initiate async open of a rpc connection to a rpc pipe on SMB using + the binding structure to determine the endpoint and options +*/ +static struct composite_context *dcerpc_pipe_connect_ncacn_np_smb_send(TALLOC_CTX *mem_ctx, struct dcerpc_pipe_connect *io, struct loadparm_context *lp_ctx) +{ + struct composite_context *c; + struct pipe_np_smb_state *s; + struct tevent_req *subreq = NULL; + struct smb_composite_connect *conn; + uint32_t flags; + const char *target_hostname = NULL; + const char *dest_address = NULL; + const char *calling_name = NULL; + + /* composite context allocation and setup */ + c = composite_create(mem_ctx, io->conn->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct pipe_np_smb_state); + if (composite_nomem(s, c)) return c; + c->private_data = s; + + s->io = *io; + conn = &s->conn; + + if (smbXcli_conn_is_connected(s->io.smb.conn)) { + continue_smb_open(c); + return c; + } + + if (s->io.creds == NULL) { + composite_error(c, NT_STATUS_INVALID_PARAMETER_MIX); + return c; + } + + /* prepare smb connection parameters: we're connecting to IPC$ share on + remote rpc server */ + target_hostname = dcerpc_binding_get_string_option(s->io.binding, "target_hostname"); + conn->in.dest_host = dcerpc_binding_get_string_option(s->io.binding, "host"); + conn->in.dest_ports = lpcfg_smb_ports(lp_ctx); + conn->in.called_name = target_hostname; + if (conn->in.called_name == NULL) { + conn->in.called_name = "*SMBSERVER"; + } + conn->in.socket_options = lpcfg_socket_options(lp_ctx); + conn->in.service = "IPC$"; + conn->in.service_type = NULL; + conn->in.workgroup = lpcfg_workgroup(lp_ctx); + conn->in.gensec_settings = lpcfg_gensec_settings(conn, lp_ctx); + + lpcfg_smbcli_options(lp_ctx, &conn->in.options); + lpcfg_smbcli_session_options(lp_ctx, &conn->in.session_options); + + /* + * provide proper credentials - user supplied, but allow a + * fallback to anonymous if this is an schannel connection + * (might be NT4 not allowing machine logins at session + * setup) or if asked to do so by the caller (perhaps a SAMR password change?) + */ + s->conn.in.credentials = s->io.creds; + flags = dcerpc_binding_get_flags(s->io.binding); + if (flags & (DCERPC_SCHANNEL|DCERPC_ANON_FALLBACK)) { + conn->in.fallback_to_anonymous = true; + } else { + conn->in.fallback_to_anonymous = false; + } + + conn->in.options.min_protocol = lpcfg_client_ipc_min_protocol(lp_ctx); + conn->in.options.max_protocol = lpcfg_client_ipc_max_protocol(lp_ctx); + if ((flags & DCERPC_SMB1) && (flags & DCERPC_SMB2)) { + /* auto */ + } else if (flags & DCERPC_SMB2) { + if (conn->in.options.min_protocol < PROTOCOL_SMB2_02) { + conn->in.options.min_protocol = PROTOCOL_SMB2_02; + } + if (conn->in.options.max_protocol < PROTOCOL_SMB2_02) { + conn->in.options.max_protocol = PROTOCOL_LATEST; + } + } else if (flags & DCERPC_SMB1) { + conn->in.options.min_protocol = PROTOCOL_NT1; + conn->in.options.max_protocol = PROTOCOL_NT1; + } else { + /* auto */ + } + + conn->in.options.signing = lpcfg_client_ipc_signing(lp_ctx); + + if (s->conn.in.credentials != NULL) { + calling_name = cli_credentials_get_workstation(s->conn.in.credentials); + } + if (calling_name == NULL) { + calling_name = "SMBCLIENT"; + } + + if (target_hostname == NULL) { + target_hostname = conn->in.dest_host; + } + + if (conn->in.dest_host != NULL && is_ipaddress(conn->in.dest_host)) { + dest_address = conn->in.dest_host; + } + + subreq = smb_connect_nego_send(s, + c->event_ctx, + s->io.resolve_ctx, + &conn->in.options, + conn->in.socket_options, + conn->in.dest_host, + dest_address, + conn->in.dest_ports, + target_hostname, + conn->in.called_name, + calling_name); + if (composite_nomem(subreq, c)) return c; + tevent_req_set_callback(subreq, + continue_smbXcli_connect, + c); + + return c; +} + +static void continue_smbXcli_connect(struct tevent_req *subreq) +{ + struct composite_context *c = + tevent_req_callback_data(subreq, + struct composite_context); + struct pipe_np_smb_state *s = + talloc_get_type_abort(c->private_data, + struct pipe_np_smb_state); + struct smb_composite_connect *conn = &s->conn; + struct composite_context *creq = NULL; + enum protocol_types protocol; + + c->status = smb_connect_nego_recv(subreq, s, + &conn->in.existing_conn); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + protocol = smbXcli_conn_protocol(conn->in.existing_conn); + if (protocol >= PROTOCOL_SMB2_02) { + /* + * continue with smb2 session setup/tree connect + * on the established connection. + */ + subreq = smb2_connect_send(s, c->event_ctx, + conn->in.dest_host, + conn->in.dest_ports, + conn->in.service, + s->io.resolve_ctx, + conn->in.credentials, + conn->in.fallback_to_anonymous, + &conn->in.existing_conn, + 0, /* previous_session_id */ + &conn->in.options, + conn->in.socket_options, + conn->in.gensec_settings); + if (composite_nomem(subreq, c)) return; + tevent_req_set_callback(subreq, continue_smb2_connect, c); + return; + } + + /* + * continue with smb1 session setup/tree connect + * on the established connection. + */ + creq = smb_composite_connect_send(conn, s->io.conn, + s->io.resolve_ctx, + c->event_ctx); + if (composite_nomem(creq, c)) return; + + composite_continue(c, creq, continue_smb_connect, c); + return; +} + + +/* + Receive result of a rpc connection to a rpc pipe on SMB +*/ +static NTSTATUS dcerpc_pipe_connect_ncacn_np_smb_recv(struct composite_context *c) +{ + NTSTATUS status = composite_wait(c); + + talloc_free(c); + return status; +} + +/* + Stage 2 of ncacn_np_smb2: Open a named pipe after successful smb2 connection +*/ +static void continue_smb2_connect(struct tevent_req *subreq) +{ + struct composite_context *c = + tevent_req_callback_data(subreq, + struct composite_context); + struct pipe_np_smb_state *s = talloc_get_type(c->private_data, + struct pipe_np_smb_state); + struct smb2_tree *t; + + /* receive result of smb2 connect request */ + c->status = smb2_connect_recv(subreq, s->io.conn, &t); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + s->io.smb.conn = t->session->transport->conn; + s->io.smb.session = t->session->smbXcli; + s->io.smb.tcon = t->smbXcli; + s->io.smb.pipe_name = dcerpc_binding_get_string_option(s->io.binding, + "endpoint"); + + continue_smb_open(c); +} + + +struct pipe_ip_tcp_state { + struct dcerpc_pipe_connect io; + const char *localaddr; + const char *host; + const char *target_hostname; + uint32_t port; +}; + + +/* + Stage 2 of ncacn_ip_tcp: rpc pipe opened (or not) +*/ +static void continue_pipe_open_ncacn_ip_tcp(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + struct pipe_ip_tcp_state *s = talloc_get_type(c->private_data, + struct pipe_ip_tcp_state); + char *localaddr = NULL; + char *remoteaddr = NULL; + + /* receive result of named pipe open request on tcp/ip */ + c->status = dcerpc_pipe_open_tcp_recv(ctx, s, &localaddr, &remoteaddr); + if (!composite_is_ok(c)) return; + + c->status = dcerpc_binding_set_string_option(s->io.binding, + "localaddress", + localaddr); + if (!composite_is_ok(c)) return; + + c->status = dcerpc_binding_set_string_option(s->io.binding, + "host", + remoteaddr); + if (!composite_is_ok(c)) return; + + composite_done(c); +} + + +/* + Initiate async open of a rpc connection to a rpc pipe on TCP/IP using + the binding structure to determine the endpoint and options +*/ +static struct composite_context* dcerpc_pipe_connect_ncacn_ip_tcp_send(TALLOC_CTX *mem_ctx, + struct dcerpc_pipe_connect *io) +{ + struct composite_context *c; + struct pipe_ip_tcp_state *s; + struct composite_context *pipe_req; + const char *endpoint; + + /* composite context allocation and setup */ + c = composite_create(mem_ctx, io->conn->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct pipe_ip_tcp_state); + if (composite_nomem(s, c)) return c; + c->private_data = s; + + /* store input parameters in state structure */ + s->io = *io; + s->localaddr = dcerpc_binding_get_string_option(io->binding, + "localaddress"); + s->host = dcerpc_binding_get_string_option(io->binding, "host"); + s->target_hostname = dcerpc_binding_get_string_option(io->binding, + "target_hostname"); + endpoint = dcerpc_binding_get_string_option(io->binding, "endpoint"); + /* port number is a binding endpoint here */ + if (endpoint != NULL) { + s->port = atoi(endpoint); + } + + if (s->port == 0) { + composite_error(c, NT_STATUS_INVALID_PARAMETER_MIX); + return c; + } + + /* send pipe open request on tcp/ip */ + pipe_req = dcerpc_pipe_open_tcp_send(s->io.conn, s->localaddr, s->host, s->target_hostname, + s->port, io->resolve_ctx); + composite_continue(c, pipe_req, continue_pipe_open_ncacn_ip_tcp, c); + return c; +} + + +/* + Receive result of a rpc connection to a rpc pipe on TCP/IP +*/ +static NTSTATUS dcerpc_pipe_connect_ncacn_ip_tcp_recv(struct composite_context *c) +{ + NTSTATUS status = composite_wait(c); + + talloc_free(c); + return status; +} + + +struct pipe_http_state { + struct dcerpc_pipe_connect io; + const char *localaddr; + const char *target_hostname; + const char *rpc_server; + uint32_t rpc_server_port; + char *rpc_proxy; + uint32_t rpc_proxy_port; + char *http_proxy; + uint32_t http_proxy_port; + bool use_tls; + bool use_proxy; + enum http_auth_method http_auth; + struct loadparm_context *lp_ctx; +}; + +/* + Stage 2 of ncacn_http: rpc pipe opened (or not) + */ +static void continue_pipe_open_ncacn_http(struct tevent_req *subreq) +{ + struct composite_context *c = NULL; + struct pipe_http_state *s = NULL; + struct tstream_context *stream = NULL; + struct tevent_queue *queue = NULL; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type(c->private_data, struct pipe_http_state); + + /* receive result of RoH connect request */ + c->status = dcerpc_pipe_open_roh_recv(subreq, s->io.conn, + &stream, &queue); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + s->io.conn->transport.transport = NCACN_HTTP; + s->io.conn->transport.stream = stream; + s->io.conn->transport.write_queue = queue; + s->io.conn->transport.pending_reads = 0; + s->io.conn->server_name = strupper_talloc(s->io.conn, + s->target_hostname); + + composite_done(c); +} + +/* + Initiate async open of a rpc connection to a rpc pipe using HTTP transport, + and using the binding structure to determine the endpoint and options +*/ +static struct composite_context* dcerpc_pipe_connect_ncacn_http_send( + TALLOC_CTX *mem_ctx, struct dcerpc_pipe_connect *io, + struct loadparm_context *lp_ctx) +{ + struct composite_context *c; + struct pipe_http_state *s; + struct tevent_req *subreq; + const char *endpoint; + const char *use_proxy; + char *proxy; + char *port; + const char *opt; + + /* composite context allocation and setup */ + c = composite_create(mem_ctx, io->conn->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct pipe_http_state); + if (composite_nomem(s, c)) return c; + c->private_data = s; + + /* store input parameters in state structure */ + s->lp_ctx = lp_ctx; + s->io = *io; + s->localaddr = dcerpc_binding_get_string_option(io->binding, + "localaddress"); + /* RPC server and port (the endpoint) */ + s->rpc_server = dcerpc_binding_get_string_option(io->binding, "host"); + s->target_hostname = dcerpc_binding_get_string_option(io->binding, + "target_hostname"); + endpoint = dcerpc_binding_get_string_option(io->binding, "endpoint"); + if (endpoint == NULL) { + composite_error(c, NT_STATUS_INVALID_PARAMETER_MIX); + return c; + } + s->rpc_server_port = atoi(endpoint); + if (s->rpc_server_port == 0) { + composite_error(c, NT_STATUS_INVALID_PARAMETER_MIX); + return c; + } + + /* Use TLS */ + opt = dcerpc_binding_get_string_option(io->binding, "HttpUseTls"); + if (opt) { + if (strcasecmp(opt, "true") == 0) { + s->use_tls = true; + } else if (strcasecmp(opt, "false") == 0) { + s->use_tls = false; + } else { + composite_error(c, NT_STATUS_INVALID_PARAMETER_MIX); + return c; + } + } else { + s->use_tls = true; + } + + /* RPC Proxy */ + proxy = port = talloc_strdup(s, dcerpc_binding_get_string_option( + io->binding, "RpcProxy")); + s->rpc_proxy = strsep(&port, ":"); + if (proxy && port) { + s->rpc_proxy_port = atoi(port); + } else { + s->rpc_proxy_port = s->use_tls ? 443 : 80; + } + if (s->rpc_proxy == NULL) { + s->rpc_proxy = talloc_strdup(s, s->rpc_server); + if (composite_nomem(s->rpc_proxy, c)) return c; + } + + /* HTTP Proxy */ + proxy = port = talloc_strdup(s, dcerpc_binding_get_string_option( + io->binding, "HttpProxy")); + s->http_proxy = strsep(&port, ":"); + if (proxy && port) { + s->http_proxy_port = atoi(port); + } else { + s->http_proxy_port = s->use_tls ? 443 : 80; + } + + /* Use local proxy */ + use_proxy = dcerpc_binding_get_string_option(io->binding, + "HttpConnectOption"); + if (use_proxy && strcasecmp(use_proxy, "UseHttpProxy")) { + s->use_proxy = true; + } + + /* If use local proxy set, the http proxy should be provided */ + if (s->use_proxy && !s->http_proxy) { + composite_error(c, NT_STATUS_INVALID_PARAMETER_MIX); + return c; + } + + /* Check which HTTP authentication method to use */ + opt = dcerpc_binding_get_string_option(io->binding, "HttpAuthOption"); + if (opt) { + if (strcasecmp(opt, "basic") == 0) { + s->http_auth = HTTP_AUTH_BASIC; + } else if (strcasecmp(opt, "ntlm") == 0) { + s->http_auth = HTTP_AUTH_NTLM; + } else if (strcasecmp(opt, "negotiate") == 0) { + s->http_auth = HTTP_AUTH_NEGOTIATE; + } else { + composite_error(c, NT_STATUS_INVALID_PARAMETER_MIX); + return c; + } + } else { + s->http_auth = HTTP_AUTH_NTLM; + } + + subreq = dcerpc_pipe_open_roh_send(s->io.conn, s->localaddr, + s->rpc_server, s->rpc_server_port, + s->rpc_proxy, s->rpc_proxy_port, + s->http_proxy, s->http_proxy_port, + s->use_tls, s->use_proxy, + s->io.creds, io->resolve_ctx, + s->lp_ctx, s->http_auth); + if (composite_nomem(subreq, c)) return c; + + tevent_req_set_callback(subreq, continue_pipe_open_ncacn_http, c); + return c; +} + +static NTSTATUS dcerpc_pipe_connect_ncacn_http_recv(struct composite_context *c) +{ + return composite_wait_free(c); +} + + +struct pipe_unix_state { + struct dcerpc_pipe_connect io; + const char *path; +}; + + +/* + Stage 2 of ncacn_unix: rpc pipe opened (or not) +*/ +static void continue_pipe_open_ncacn_unix_stream(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + + /* receive result of pipe open request on unix socket */ + c->status = dcerpc_pipe_open_unix_stream_recv(ctx); + if (!composite_is_ok(c)) return; + + composite_done(c); +} + + +/* + Initiate async open of a rpc connection to a rpc pipe on unix socket using + the binding structure to determine the endpoint and options +*/ +static struct composite_context* dcerpc_pipe_connect_ncacn_unix_stream_send(TALLOC_CTX *mem_ctx, + struct dcerpc_pipe_connect *io) +{ + struct composite_context *c; + struct pipe_unix_state *s; + struct composite_context *pipe_req; + + /* composite context allocation and setup */ + c = composite_create(mem_ctx, io->conn->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct pipe_unix_state); + if (composite_nomem(s, c)) return c; + c->private_data = s; + + /* prepare pipe open parameters and store them in state structure + also, verify whether biding endpoint is not null */ + s->io = *io; + + s->path = dcerpc_binding_get_string_option(io->binding, "endpoint"); + if (s->path == NULL) { + composite_error(c, NT_STATUS_INVALID_PARAMETER_MIX); + return c; + } + + /* send pipe open request on unix socket */ + pipe_req = dcerpc_pipe_open_unix_stream_send(s->io.conn, s->path); + composite_continue(c, pipe_req, continue_pipe_open_ncacn_unix_stream, c); + return c; +} + + +/* + Receive result of a rpc connection to a pipe on unix socket +*/ +static NTSTATUS dcerpc_pipe_connect_ncacn_unix_stream_recv(struct composite_context *c) +{ + NTSTATUS status = composite_wait(c); + + talloc_free(c); + return status; +} + + +struct pipe_ncalrpc_state { + struct dcerpc_pipe_connect io; +}; + +static NTSTATUS dcerpc_pipe_connect_ncalrpc_recv(struct composite_context *c); + +/* + Stage 2 of ncalrpc: rpc pipe opened (or not) +*/ +static void continue_pipe_open_ncalrpc(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + + /* receive result of pipe open request on ncalrpc */ + c->status = dcerpc_pipe_connect_ncalrpc_recv(ctx); + if (!composite_is_ok(c)) return; + + composite_done(c); +} + + +/* + Initiate async open of a rpc connection request on NCALRPC using + the binding structure to determine the endpoint and options +*/ +static struct composite_context* dcerpc_pipe_connect_ncalrpc_send(TALLOC_CTX *mem_ctx, + struct dcerpc_pipe_connect *io) +{ + struct composite_context *c; + struct pipe_ncalrpc_state *s; + struct composite_context *pipe_req; + const char *endpoint; + + /* composite context allocation and setup */ + c = composite_create(mem_ctx, io->conn->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct pipe_ncalrpc_state); + if (composite_nomem(s, c)) return c; + c->private_data = s; + + /* store input parameters in state structure */ + s->io = *io; + + endpoint = dcerpc_binding_get_string_option(io->binding, "endpoint"); + if (endpoint == NULL) { + composite_error(c, NT_STATUS_INVALID_PARAMETER_MIX); + return c; + } + + /* send pipe open request */ + pipe_req = dcerpc_pipe_open_pipe_send(s->io.conn, + s->io.ncalrpc.dir, + endpoint); + composite_continue(c, pipe_req, continue_pipe_open_ncalrpc, c); + return c; +} + + +/* + Receive result of a rpc connection to a rpc pipe on NCALRPC +*/ +static NTSTATUS dcerpc_pipe_connect_ncalrpc_recv(struct composite_context *c) +{ + NTSTATUS status = composite_wait(c); + + talloc_free(c); + return status; +} + + +struct pipe_connect_state { + struct dcerpc_pipe *pipe; + struct dcerpc_binding *binding; + const struct ndr_interface_table *table; + struct cli_credentials *credentials; + struct loadparm_context *lp_ctx; +}; + + +static void continue_map_binding(struct composite_context *ctx); +static void continue_connect(struct composite_context *c, struct pipe_connect_state *s); +static void continue_pipe_connect_ncacn_np_smb(struct composite_context *ctx); +static void continue_pipe_connect_ncacn_ip_tcp(struct composite_context *ctx); +static void continue_pipe_connect_ncacn_http(struct composite_context *ctx); +static void continue_pipe_connect_ncacn_unix(struct composite_context *ctx); +static void continue_pipe_connect_ncalrpc(struct composite_context *ctx); +static void continue_pipe_connect(struct composite_context *c, struct pipe_connect_state *s); +static void continue_pipe_auth(struct composite_context *ctx); + + +/* + Stage 2 of pipe_connect_b: Receive result of endpoint mapping +*/ +static void continue_map_binding(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + struct pipe_connect_state *s = talloc_get_type(c->private_data, + struct pipe_connect_state); + const char *endpoint; + + c->status = dcerpc_epm_map_binding_recv(ctx); + if (!composite_is_ok(c)) return; + + endpoint = dcerpc_binding_get_string_option(s->binding, "endpoint"); + DEBUG(4,("Mapped to DCERPC endpoint %s\n", endpoint)); + + continue_connect(c, s); +} + + +/* + Stage 2 of pipe_connect_b: Continue connection after endpoint is known +*/ +static void continue_connect(struct composite_context *c, struct pipe_connect_state *s) +{ + struct dcerpc_pipe_connect pc; + + /* potential exits to another stage by sending an async request */ + struct composite_context *ncacn_np_smb_req; + struct composite_context *ncacn_ip_tcp_req; + struct composite_context *ncacn_http_req; + struct composite_context *ncacn_unix_req; + struct composite_context *ncalrpc_req; + enum dcerpc_transport_t transport; + + /* dcerpc pipe connect input parameters */ + ZERO_STRUCT(pc); + pc.conn = s->pipe->conn; + pc.binding = s->binding; + pc.interface = s->table; + pc.creds = s->credentials; + pc.resolve_ctx = lpcfg_resolve_context(s->lp_ctx); + + transport = dcerpc_binding_get_transport(s->binding); + + /* connect dcerpc pipe depending on required transport */ + switch (transport) { + case NCACN_NP: + /* + * SMB1/2/3... + */ + ncacn_np_smb_req = dcerpc_pipe_connect_ncacn_np_smb_send(c, &pc, s->lp_ctx); + composite_continue(c, ncacn_np_smb_req, continue_pipe_connect_ncacn_np_smb, c); + return; + + case NCACN_IP_TCP: + ncacn_ip_tcp_req = dcerpc_pipe_connect_ncacn_ip_tcp_send(c, &pc); + composite_continue(c, ncacn_ip_tcp_req, continue_pipe_connect_ncacn_ip_tcp, c); + return; + + case NCACN_HTTP: + ncacn_http_req = dcerpc_pipe_connect_ncacn_http_send(c, &pc, s->lp_ctx); + composite_continue(c, ncacn_http_req, continue_pipe_connect_ncacn_http, c); + return; + + case NCACN_UNIX_STREAM: + ncacn_unix_req = dcerpc_pipe_connect_ncacn_unix_stream_send(c, &pc); + composite_continue(c, ncacn_unix_req, continue_pipe_connect_ncacn_unix, c); + return; + + case NCALRPC: + pc.ncalrpc.dir = lpcfg_ncalrpc_dir(s->lp_ctx); + c->status = dcerpc_binding_set_string_option(s->binding, "ncalrpc_dir", + pc.ncalrpc.dir); + if (!composite_is_ok(c)) return; + ncalrpc_req = dcerpc_pipe_connect_ncalrpc_send(c, &pc); + composite_continue(c, ncalrpc_req, continue_pipe_connect_ncalrpc, c); + return; + + default: + /* looks like a transport we don't support now */ + composite_error(c, NT_STATUS_NOT_SUPPORTED); + } +} + + +/* + Stage 3 of pipe_connect_b: Receive result of pipe connect request on + named pipe on smb +*/ +static void continue_pipe_connect_ncacn_np_smb(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + struct pipe_connect_state *s = talloc_get_type(c->private_data, + struct pipe_connect_state); + + c->status = dcerpc_pipe_connect_ncacn_np_smb_recv(ctx); + if (!composite_is_ok(c)) return; + + continue_pipe_connect(c, s); +} + + +/* + Stage 3 of pipe_connect_b: Receive result of pipe connect request on tcp/ip +*/ +static void continue_pipe_connect_ncacn_ip_tcp(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + struct pipe_connect_state *s = talloc_get_type(c->private_data, + struct pipe_connect_state); + + c->status = dcerpc_pipe_connect_ncacn_ip_tcp_recv(ctx); + if (!composite_is_ok(c)) return; + + continue_pipe_connect(c, s); +} + + +/* + Stage 3 of pipe_connect_b: Receive result of pipe connect request on http +*/ +static void continue_pipe_connect_ncacn_http(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + struct pipe_connect_state *s = talloc_get_type(c->private_data, + struct pipe_connect_state); + + c->status = dcerpc_pipe_connect_ncacn_http_recv(ctx); + if (!composite_is_ok(c)) return; + + continue_pipe_connect(c, s); +} + + +/* + Stage 3 of pipe_connect_b: Receive result of pipe connect request on unix socket +*/ +static void continue_pipe_connect_ncacn_unix(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + struct pipe_connect_state *s = talloc_get_type(c->private_data, + struct pipe_connect_state); + + c->status = dcerpc_pipe_connect_ncacn_unix_stream_recv(ctx); + if (!composite_is_ok(c)) return; + + continue_pipe_connect(c, s); +} + + +/* + Stage 3 of pipe_connect_b: Receive result of pipe connect request on local rpc +*/ +static void continue_pipe_connect_ncalrpc(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + struct pipe_connect_state *s = talloc_get_type(c->private_data, + struct pipe_connect_state); + + c->status = dcerpc_pipe_connect_ncalrpc_recv(ctx); + if (!composite_is_ok(c)) return; + + continue_pipe_connect(c, s); +} + + +/* + Stage 4 of pipe_connect_b: Start an authentication on connected dcerpc pipe + depending on credentials and binding flags passed. +*/ +static void continue_pipe_connect(struct composite_context *c, struct pipe_connect_state *s) +{ + struct composite_context *auth_bind_req; + + s->pipe->binding = dcerpc_binding_dup(s->pipe, s->binding); + if (composite_nomem(s->pipe->binding, c)) { + return; + } + + auth_bind_req = dcerpc_pipe_auth_send(s->pipe, s->binding, s->table, + s->credentials, s->lp_ctx); + composite_continue(c, auth_bind_req, continue_pipe_auth, c); +} + + +/* + Stage 5 of pipe_connect_b: Receive result of pipe authentication request + and say if all went ok +*/ +static void continue_pipe_auth(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + struct pipe_connect_state *s = talloc_get_type(c->private_data, struct pipe_connect_state); + + c->status = dcerpc_pipe_auth_recv(ctx, s, &s->pipe); + if (!composite_is_ok(c)) return; + + composite_done(c); +} + + +/* + handle timeouts of a dcerpc connect +*/ +static void dcerpc_connect_timeout_handler(struct tevent_context *ev, struct tevent_timer *te, + struct timeval t, void *private_data) +{ + struct composite_context *c = talloc_get_type_abort(private_data, + struct composite_context); + struct pipe_connect_state *s = talloc_get_type_abort(c->private_data, struct pipe_connect_state); + if (!s->pipe->inhibit_timeout_processing) { + composite_error(c, NT_STATUS_IO_TIMEOUT); + } else { + s->pipe->timed_out = true; + } +} + +/* + start a request to open a rpc connection to a rpc pipe, using + specified binding structure to determine the endpoint and options +*/ +_PUBLIC_ struct composite_context* dcerpc_pipe_connect_b_send(TALLOC_CTX *parent_ctx, + const struct dcerpc_binding *binding, + const struct ndr_interface_table *table, + struct cli_credentials *credentials, + struct tevent_context *ev, + struct loadparm_context *lp_ctx) +{ + struct composite_context *c; + struct pipe_connect_state *s; + enum dcerpc_transport_t transport; + const char *endpoint = NULL; + struct cli_credentials *epm_creds = NULL; + + /* composite context allocation and setup */ + c = composite_create(parent_ctx, ev); + if (c == NULL) { + return NULL; + } + + s = talloc_zero(c, struct pipe_connect_state); + if (composite_nomem(s, c)) return c; + c->private_data = s; + + /* initialise dcerpc pipe structure */ + s->pipe = dcerpc_pipe_init(c, ev); + if (composite_nomem(s->pipe, c)) return c; + + if (DEBUGLEVEL >= 10) + s->pipe->conn->packet_log_dir = lpcfg_lock_directory(lp_ctx); + + /* store parameters in state structure */ + s->binding = dcerpc_binding_dup(s, binding); + if (composite_nomem(s->binding, c)) return c; + s->table = table; + s->credentials = credentials; + s->lp_ctx = lp_ctx; + + s->pipe->timed_out = false; + s->pipe->inhibit_timeout_processing = false; + + tevent_add_timer(c->event_ctx, c, + timeval_current_ofs(DCERPC_REQUEST_TIMEOUT, 0), + dcerpc_connect_timeout_handler, c); + + transport = dcerpc_binding_get_transport(s->binding); + + switch (transport) { + case NCACN_NP: + case NCACN_IP_TCP: + case NCALRPC: + endpoint = dcerpc_binding_get_string_option(s->binding, "endpoint"); + + /* anonymous credentials for rpc connection used to get endpoint mapping */ + epm_creds = cli_credentials_init_anon(s); + if (composite_nomem(epm_creds, c)) return c; + + break; + case NCACN_HTTP: + endpoint = dcerpc_binding_get_string_option(s->binding, "endpoint"); + epm_creds = credentials; + break; + default: + DBG_INFO("Unknown transport; continuing with anon, no endpoint.\n"); + epm_creds = cli_credentials_init_anon(s); + if (composite_nomem(epm_creds, c)){ + return c; + } + break; + } + + if (endpoint == NULL) { + struct composite_context *binding_req; + + binding_req = dcerpc_epm_map_binding_send(c, s->binding, s->table, + epm_creds, + s->pipe->conn->event_ctx, + s->lp_ctx); + composite_continue(c, binding_req, continue_map_binding, c); + return c; + } + + continue_connect(c, s); + return c; +} + + +/* + receive result of a request to open a rpc connection to a rpc pipe +*/ +_PUBLIC_ NTSTATUS dcerpc_pipe_connect_b_recv(struct composite_context *c, TALLOC_CTX *mem_ctx, + struct dcerpc_pipe **p) +{ + NTSTATUS status; + struct pipe_connect_state *s; + + status = composite_wait(c); + + if (NT_STATUS_IS_OK(status)) { + s = talloc_get_type(c->private_data, struct pipe_connect_state); + talloc_steal(mem_ctx, s->pipe); + *p = s->pipe; + } + talloc_free(c); + return status; +} + + +/* + open a rpc connection to a rpc pipe, using the specified + binding structure to determine the endpoint and options - sync version +*/ +_PUBLIC_ NTSTATUS dcerpc_pipe_connect_b(TALLOC_CTX *parent_ctx, + struct dcerpc_pipe **pp, + const struct dcerpc_binding *binding, + const struct ndr_interface_table *table, + struct cli_credentials *credentials, + struct tevent_context *ev, + struct loadparm_context *lp_ctx) +{ + struct composite_context *c; + + c = dcerpc_pipe_connect_b_send(parent_ctx, binding, table, + credentials, ev, lp_ctx); + return dcerpc_pipe_connect_b_recv(c, parent_ctx, pp); +} + + +struct pipe_conn_state { + struct dcerpc_pipe *pipe; +}; + + +static void continue_pipe_connect_b(struct composite_context *ctx); + + +/* + Initiate rpc connection to a rpc pipe, using the specified string + binding to determine the endpoint and options. + The string is to be parsed to a binding structure first. +*/ +_PUBLIC_ struct composite_context* dcerpc_pipe_connect_send(TALLOC_CTX *parent_ctx, + const char *binding, + const struct ndr_interface_table *table, + struct cli_credentials *credentials, + struct tevent_context *ev, struct loadparm_context *lp_ctx) +{ + struct composite_context *c; + struct pipe_conn_state *s; + struct dcerpc_binding *b; + struct composite_context *pipe_conn_req; + + /* composite context allocation and setup */ + c = composite_create(parent_ctx, ev); + if (c == NULL) { + return NULL; + } + + s = talloc_zero(c, struct pipe_conn_state); + if (composite_nomem(s, c)) return c; + c->private_data = s; + + /* parse binding string to the structure */ + c->status = dcerpc_parse_binding(c, binding, &b); + if (!NT_STATUS_IS_OK(c->status)) { + DEBUG(0, ("Failed to parse dcerpc binding '%s'\n", binding)); + composite_error(c, c->status); + return c; + } + + DEBUG(3, ("Using binding %s\n", dcerpc_binding_string(c, b))); + + /* + start connecting to a rpc pipe after binding structure + is established + */ + pipe_conn_req = dcerpc_pipe_connect_b_send(c, b, table, + credentials, ev, lp_ctx); + composite_continue(c, pipe_conn_req, continue_pipe_connect_b, c); + return c; +} + + +/* + Stage 2 of pipe_connect: Receive result of actual pipe connect request + and say if we're done ok +*/ +static void continue_pipe_connect_b(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + struct pipe_conn_state *s = talloc_get_type(c->private_data, + struct pipe_conn_state); + + c->status = dcerpc_pipe_connect_b_recv(ctx, c, &s->pipe); + talloc_steal(s, s->pipe); + if (!composite_is_ok(c)) return; + + composite_done(c); +} + + +/* + Receive result of pipe connect (using binding string) request + and return connected pipe structure. +*/ +NTSTATUS dcerpc_pipe_connect_recv(struct composite_context *c, + TALLOC_CTX *mem_ctx, + struct dcerpc_pipe **pp) +{ + NTSTATUS status; + struct pipe_conn_state *s; + + status = composite_wait(c); + if (NT_STATUS_IS_OK(status)) { + s = talloc_get_type(c->private_data, struct pipe_conn_state); + *pp = talloc_steal(mem_ctx, s->pipe); + } + talloc_free(c); + return status; +} + + +/* + Open a rpc connection to a rpc pipe, using the specified string + binding to determine the endpoint and options - sync version +*/ +_PUBLIC_ NTSTATUS dcerpc_pipe_connect(TALLOC_CTX *parent_ctx, + struct dcerpc_pipe **pp, + const char *binding, + const struct ndr_interface_table *table, + struct cli_credentials *credentials, + struct tevent_context *ev, + struct loadparm_context *lp_ctx) +{ + struct composite_context *c; + c = dcerpc_pipe_connect_send(parent_ctx, binding, + table, credentials, ev, lp_ctx); + return dcerpc_pipe_connect_recv(c, parent_ctx, pp); +} + diff --git a/source4/librpc/rpc/dcerpc_roh.c b/source4/librpc/rpc/dcerpc_roh.c new file mode 100644 index 0000000..3aa7551 --- /dev/null +++ b/source4/librpc/rpc/dcerpc_roh.c @@ -0,0 +1,914 @@ +/* + Unix SMB/CIFS implementation. + + [MS-RPCH] - RPC over HTTP client + + Copyright (C) 2013 Samuel Cabrero <samuelcabrero@kernevil.me> + + 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 "lib/events/events.h" +#include "lib/util/tevent_ntstatus.h" +#include "lib/tls/tls.h" +#include "libcli/resolve/resolve.h" +#include "libcli/composite/composite.h" +#include "auth/credentials/credentials.h" +#include "tsocket/tsocket.h" +#include "tsocket/tsocket_internal.h" +#include "librpc/rpc/dcerpc.h" +#include "librpc/rpc/dcerpc_roh.h" +#include "librpc/rpc/dcerpc_proto.h" +#include "lib/param/param.h" +#include "libcli/http/http.h" +#include "lib/util/util_net.h" + +static ssize_t tstream_roh_pending_bytes(struct tstream_context *stream); +static struct tevent_req * tstream_roh_readv_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *stream, + struct iovec *vector, + size_t count); +static int tstream_roh_readv_recv(struct tevent_req *req, int *perrno); +static struct tevent_req * tstream_roh_writev_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *stream, + const struct iovec *vector, + size_t count); +static int tstream_roh_writev_recv(struct tevent_req *req, int *perrno); +static struct tevent_req * tstream_roh_disconnect_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *stream); +static int tstream_roh_disconnect_recv(struct tevent_req *req, int *perrno); + +static const struct tstream_context_ops tstream_roh_ops = { + .name = "roh", + .pending_bytes = tstream_roh_pending_bytes, + .readv_send = tstream_roh_readv_send, + .readv_recv = tstream_roh_readv_recv, + .writev_send = tstream_roh_writev_send, + .writev_recv = tstream_roh_writev_recv, + .disconnect_send = tstream_roh_disconnect_send, + .disconnect_recv = tstream_roh_disconnect_recv, +}; + +struct tstream_roh_context { + struct roh_connection *roh_conn; +}; + +struct roh_open_connection_state { + struct tevent_req *req; + struct tevent_context *event_ctx; + struct cli_credentials *credentials; + struct resolve_context *resolve_ctx; + const char **rpcproxy_addresses; + unsigned int rpcproxy_address_index; + + struct dcecli_connection *conn; + bool tls; + + const char *rpc_proxy; + unsigned int rpc_proxy_port; + const char *rpc_server; + unsigned int rpc_server_port; + const char *target_hostname; + + struct roh_connection *roh; + struct tstream_tls_params *tls_params; + struct loadparm_context *lp_ctx; + uint8_t http_auth; +}; + +NTSTATUS dcerpc_pipe_open_roh_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct tstream_context **stream, + struct tevent_queue **queue) +{ + struct roh_open_connection_state *state; + struct tstream_roh_context *roh_stream_ctx; + NTSTATUS status; + + state = tevent_req_data(req, struct roh_open_connection_state); + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *stream = tstream_context_create(mem_ctx, &tstream_roh_ops, + &roh_stream_ctx, + struct tstream_roh_context, + __location__); + if (!stream) { + tevent_req_received(req); + return NT_STATUS_NO_MEMORY; + } + ZERO_STRUCTP(roh_stream_ctx); + + roh_stream_ctx->roh_conn = talloc_move(mem_ctx, &state->roh); + *queue = http_conn_send_queue( + roh_stream_ctx->roh_conn->default_channel_in->http_conn); + + tevent_req_received(req); + + return NT_STATUS_OK; +} + +struct roh_connect_channel_state { + struct roh_channel *channel; +}; + +static void roh_connect_channel_done(struct tevent_req *subreq); +static struct tevent_req *roh_connect_channel_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *rpcproxy_ip_address, + unsigned int rpcproxy_port, + struct cli_credentials *credentials, + bool tls, + struct tstream_tls_params *tls_params) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct roh_connect_channel_state *state = NULL; + + DBG_DEBUG("Connecting ROH channel socket, RPC proxy is " + "%s:%d (TLS: %s)\n", rpcproxy_ip_address, rpcproxy_port, + (tls ? "true" : "false")); + + req = tevent_req_create(mem_ctx, &state, + struct roh_connect_channel_state); + if (req == NULL) { + return NULL; + } + + if (!is_ipaddress(rpcproxy_ip_address)) { + DBG_ERR("Invalid host (%s), needs to be an IP address\n", + rpcproxy_ip_address); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + /* Initialize channel structure */ + state->channel = talloc_zero(state, struct roh_channel); + if (tevent_req_nomem(state->channel, req)) { + return tevent_req_post(req, ev); + } + + state->channel->channel_cookie = GUID_random(); + + subreq = http_connect_send(state, + ev, + rpcproxy_ip_address, + rpcproxy_port, + credentials, + tls ? tls_params : NULL); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, roh_connect_channel_done, req); + + return req; +} + +static void roh_connect_channel_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + struct roh_connect_channel_state *state = NULL; + NTSTATUS status; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct roh_connect_channel_state); + + ret = http_connect_recv(subreq, + state->channel, + &state->channel->http_conn); + TALLOC_FREE(subreq); + if (ret != 0) { + status = map_nt_error_from_unix_common(ret); + tevent_req_nterror(req, status); + return; + } + + DBG_DEBUG("HTTP connected\n"); + tevent_req_done(req); +} + +static NTSTATUS roh_connect_channel_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct roh_channel **channel) +{ + struct roh_connect_channel_state *state = tevent_req_data( + req, struct roh_connect_channel_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *channel = talloc_move(mem_ctx, &state->channel); + tevent_req_received(req); + + return NT_STATUS_OK; +} + +static void roh_continue_resolve_name(struct composite_context *ctx); + +/** + * Send rpc pipe open request to given host:port using http transport + */ +struct tevent_req *dcerpc_pipe_open_roh_send(struct dcecli_connection *conn, + const char *localaddr, + const char *rpc_server, + uint32_t rpc_server_port, + const char *rpc_proxy, + uint32_t rpc_proxy_port, + const char *http_proxy, + uint32_t http_proxy_port, + bool use_tls, + bool use_proxy, + struct cli_credentials *credentials, + struct resolve_context *resolve_ctx, + struct loadparm_context *lp_ctx, + uint8_t http_auth) +{ + NTSTATUS status; + struct tevent_req *req; + struct composite_context *ctx; + struct roh_open_connection_state *state; + struct nbt_name name; + + req = tevent_req_create(conn, &state, struct roh_open_connection_state); + if (req == NULL) { + return NULL; + } + + /* Set state fields */ + state->req = req; + state->event_ctx = conn->event_ctx; + state->lp_ctx = lp_ctx, + state->credentials = credentials; + state->conn = conn; + state->tls = use_tls; + + /* Initialize connection structure (3.2.1.3) */ + /* TODO Initialize virtual connection cookie table */ + state->rpc_server = talloc_strdup(state, rpc_server); + state->rpc_server_port = rpc_server_port; + state->rpc_proxy = talloc_strdup(state, rpc_proxy); + state->rpc_proxy_port = rpc_proxy_port; + state->http_auth = http_auth; + + state->roh = talloc_zero(state, struct roh_connection); + state->roh->protocol_version = ROH_V2; + state->roh->connection_state = ROH_STATE_OPEN_START; + state->roh->connection_cookie = GUID_random(); + state->roh->association_group_id_cookie = GUID_random(); + + /* Additional initialization steps (3.2.2.3) */ + state->roh->proxy_use = use_proxy; + state->roh->current_keep_alive_time = 0; + state->roh->current_keep_alive_interval = 0; + + /* Initialize TLS */ + if (use_tls) { + char *ca_file = lpcfg_tls_cafile(state, lp_ctx); + char *crl_file = lpcfg_tls_crlfile(state, lp_ctx); + const char *tls_priority = lpcfg_tls_priority(lp_ctx); + enum tls_verify_peer_state verify_peer = + lpcfg_tls_verify_peer(lp_ctx); + + status = tstream_tls_params_client(state->roh, + ca_file, crl_file, + tls_priority, + verify_peer, + state->rpc_proxy, + &state->tls_params); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("%s: Failed tstream_tls_params_client - %s\n", + __func__, nt_errstr(status))); + tevent_req_nterror(req, status); + return tevent_req_post(req, conn->event_ctx); + } + } + + /* Resolve RPC proxy server name */ + make_nbt_name_server(&name, state->rpc_proxy); + ctx = resolve_name_send(resolve_ctx, state, &name, state->event_ctx); + if (tevent_req_nomem(ctx, req)) { + return tevent_req_post(req, state->event_ctx); + } + ctx->async.fn = roh_continue_resolve_name; + ctx->async.private_data = state; + + return req; +} + +static void roh_connect_channel_in_done(struct tevent_req *subreq); +static void roh_continue_resolve_name(struct composite_context *ctx) +{ + NTSTATUS status; + struct roh_open_connection_state *state; + struct tevent_req *subreq; + + state = talloc_get_type_abort(ctx->async.private_data, + struct roh_open_connection_state); + status = resolve_name_multiple_recv(ctx, state, + &state->rpcproxy_addresses); + if (tevent_req_nterror(state->req, status)) { + DEBUG(2, ("%s: No server found: %s\n", __func__, + nt_errstr(status))); + return; + } + + state->rpcproxy_address_index = 0; + if (state->rpcproxy_addresses[state->rpcproxy_address_index] == NULL) { + DEBUG(2, ("%s: No server found\n", __func__)); + tevent_req_nterror(state->req, NT_STATUS_OBJECT_NAME_NOT_FOUND); + return; + } + + /* + * TODO Determine proxy use + * If state->roh->proxy_use == true, the client has requested to + * always use local proxy. Otherwise, run the proxy use discovery + */ + state->roh->connection_state = ROH_STATE_OPEN_START; + subreq = roh_connect_channel_send(state, + state->event_ctx, + state->rpcproxy_addresses[state->rpcproxy_address_index], + state->rpc_proxy_port, + state->credentials, + state->tls, + state->tls_params); + if (tevent_req_nomem(subreq, state->req)) { + return; + } + tevent_req_set_callback(subreq, roh_connect_channel_in_done, state->req); +} + +static void roh_connect_channel_out_done(struct tevent_req *); +static void roh_connect_channel_in_done(struct tevent_req *subreq) +{ + NTSTATUS status; + struct tevent_req *req; + struct roh_open_connection_state *state; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct roh_open_connection_state); + + status = roh_connect_channel_recv(subreq, state->roh, + &state->roh->default_channel_in); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + subreq = roh_connect_channel_send(state, + state->event_ctx, + state->rpcproxy_addresses[state->rpcproxy_address_index], + state->rpc_proxy_port, + state->credentials, + state->tls, + state->tls_params); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, roh_connect_channel_out_done, req); +} + +static void roh_send_RPC_DATA_IN_done(struct tevent_req *); +static void roh_connect_channel_out_done(struct tevent_req *subreq) +{ + NTSTATUS status; + struct tevent_req *req; + struct roh_open_connection_state *state; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct roh_open_connection_state); + + status = roh_connect_channel_recv(subreq, state->roh, + &state->roh->default_channel_out); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + subreq = roh_send_RPC_DATA_IN_send(state, state->lp_ctx, + state->event_ctx, + state->credentials, + state->roh, + state->rpc_server, + state->rpc_server_port, + state->rpc_proxy, + state->http_auth); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, roh_send_RPC_DATA_IN_done, req); +} + +static void roh_send_RPC_DATA_OUT_done(struct tevent_req *); +static void roh_send_RPC_DATA_IN_done(struct tevent_req *subreq) +{ + NTSTATUS status; + struct tevent_req *req; + struct roh_open_connection_state *state; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct roh_open_connection_state); + + status = roh_send_RPC_DATA_IN_recv(subreq); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + subreq = roh_send_RPC_DATA_OUT_send(state, + state->lp_ctx, + state->event_ctx, + state->credentials, + state->roh, + state->rpc_server, + state->rpc_server_port, + state->rpc_proxy, + state->http_auth); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, roh_send_RPC_DATA_OUT_done, req); +} + +static void roh_send_CONN_A1_done(struct tevent_req *); +static void roh_send_RPC_DATA_OUT_done(struct tevent_req *subreq) +{ + NTSTATUS status; + struct tevent_req *req; + struct roh_open_connection_state *state; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct roh_open_connection_state); + + status = roh_send_RPC_DATA_OUT_recv(subreq); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + subreq = roh_send_CONN_A1_send(state, state->event_ctx, state->roh); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, roh_send_CONN_A1_done, req); +} + +static void roh_send_CONN_B1_done(struct tevent_req *); +static void roh_send_CONN_A1_done(struct tevent_req *subreq) +{ + NTSTATUS status; + struct tevent_req *req; + struct roh_open_connection_state *state; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct roh_open_connection_state); + + status = roh_send_CONN_A1_recv(subreq); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + subreq = roh_send_CONN_B1_send(state, state->event_ctx, state->roh); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, roh_send_CONN_B1_done, req); +} + +static void roh_recv_out_channel_response_done(struct tevent_req *); +static void roh_send_CONN_B1_done(struct tevent_req *subreq) +{ + NTSTATUS status; + struct tevent_req *req; + struct roh_open_connection_state *state; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct roh_open_connection_state); + + status = roh_send_CONN_B1_recv(subreq); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + state->roh->connection_state = ROH_STATE_OUT_CHANNEL_WAIT; + subreq = roh_recv_out_channel_response_send(state, state->event_ctx, + state->roh); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, roh_recv_out_channel_response_done, req); +} + +static void roh_recv_CONN_A3_done(struct tevent_req *); +static void roh_recv_out_channel_response_done(struct tevent_req *subreq) +{ + NTSTATUS status; + char *response; + struct tevent_req *req; + struct roh_open_connection_state *state; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct roh_open_connection_state); + + status = roh_recv_out_channel_response_recv(subreq, state, &response); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + state->roh->connection_state = ROH_STATE_WAIT_A3W; + subreq = roh_recv_CONN_A3_send(state, state->event_ctx, state->roh); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, roh_recv_CONN_A3_done, req); +} + +static void roh_recv_CONN_C2_done(struct tevent_req *); +static void roh_recv_CONN_A3_done(struct tevent_req *subreq) +{ + NTSTATUS status; + struct tevent_req *req; + struct roh_open_connection_state *state; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct roh_open_connection_state); + + status = roh_recv_CONN_A3_recv(subreq, &state->roh->default_channel_out->connection_timeout); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + state->roh->connection_state = ROH_STATE_WAIT_C2; + subreq = roh_recv_CONN_C2_send(state, state->event_ctx, state->roh); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, roh_recv_CONN_C2_done, req); +} + +static void roh_recv_CONN_C2_done(struct tevent_req *subreq) +{ + NTSTATUS status; + struct tevent_req *req; + struct roh_open_connection_state *state; + unsigned int version; + unsigned int recv; + unsigned int timeout; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct roh_open_connection_state); + + status = roh_recv_CONN_C2_recv(subreq, &version, &recv, &timeout); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + state->roh->connection_state = ROH_STATE_OPENED; + + tevent_req_done(req); +} + +static ssize_t tstream_roh_pending_bytes(struct tstream_context *stream) +{ + struct tstream_roh_context *ctx = NULL; + struct tstream_context *tstream = NULL; + + ctx = tstream_context_data(stream, struct tstream_roh_context); + if (!ctx->roh_conn) { + errno = ENOTCONN; + return -1; + } + + tstream = http_conn_tstream( + ctx->roh_conn->default_channel_out->http_conn); + if (tstream == NULL) { + errno = ENOTCONN; + return -1; + } + return tstream_pending_bytes(tstream); +} + +struct tstream_roh_readv_state { + struct roh_connection *roh_conn; + int ret; +}; + +static void tstream_roh_readv_handler(struct tevent_req *subreq); +static struct tevent_req * tstream_roh_readv_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *stream, + struct iovec *vector, + size_t count) +{ + struct tstream_roh_context *ctx = NULL; + struct tstream_roh_readv_state *state; + struct tevent_req *req, *subreq; + struct tstream_context *channel_stream = NULL; + + req = tevent_req_create(mem_ctx, &state, struct tstream_roh_readv_state); + if (!req) { + return NULL; + } + + ctx = tstream_context_data(stream, struct tstream_roh_context); + if (!ctx->roh_conn) { + tevent_req_error(req, ENOTCONN); + goto post; + } + if (!ctx->roh_conn->default_channel_out) { + tevent_req_error(req, ENOTCONN); + goto post; + } + channel_stream = http_conn_tstream( + ctx->roh_conn->default_channel_out->http_conn); + if (channel_stream == NULL) { + tevent_req_error(req, ENOTCONN); + goto post; + } + + state->roh_conn = ctx->roh_conn; + + subreq = tstream_readv_send(state, ev, + channel_stream, + vector, count); + if (tevent_req_nomem(subreq, req)) { + goto post; + } + tevent_req_set_callback(subreq, tstream_roh_readv_handler, req); + + return req; +post: + tevent_req_post(req, ev); + return req; +} + +static void tstream_roh_readv_handler(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct tstream_roh_readv_state *state; + int ret; + int sys_errno; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct tstream_roh_readv_state); + ret = tstream_readv_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (ret == -1) { + tevent_req_error(req, sys_errno); + return; + } + + state->ret = ret; + + tevent_req_done(req); +} + +static int tstream_roh_readv_recv(struct tevent_req *req, int *perrno) +{ + struct tstream_roh_readv_state *state; + int ret; + + state = tevent_req_data(req, struct tstream_roh_readv_state); + ret = tsocket_simple_int_recv(req, perrno); + if (ret == 0) { + ret = state->ret; + } + + tevent_req_received(req); + return ret; +} + +struct tstream_roh_writev_state { + struct roh_connection *roh_conn; + int nwritten; +}; + +static void tstream_roh_writev_handler(struct tevent_req *subreq); +static struct tevent_req * tstream_roh_writev_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *stream, + const struct iovec *vector, + size_t count) +{ + struct tstream_roh_context *ctx = NULL; + struct tstream_roh_writev_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct tstream_context *channel_stream = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct tstream_roh_writev_state); + if (!req) { + return NULL; + } + + ctx = tstream_context_data(stream, struct tstream_roh_context); + if (!ctx->roh_conn) { + tevent_req_error(req, ENOTCONN); + goto post; + } + if (!ctx->roh_conn->default_channel_in) { + tevent_req_error(req, ENOTCONN); + goto post; + } + channel_stream = http_conn_tstream( + ctx->roh_conn->default_channel_in->http_conn); + if (channel_stream == NULL) { + tevent_req_error(req, ENOTCONN); + goto post; + } + + state->roh_conn = ctx->roh_conn; + + subreq = tstream_writev_send(state, ev, + channel_stream, + vector, count); + if (tevent_req_nomem(subreq, req)) { + goto post; + } + tevent_req_set_callback(subreq, tstream_roh_writev_handler, req); + + return req; +post: + tevent_req_post(req, ev); + return req; +} + +static void tstream_roh_writev_handler(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct tstream_roh_writev_state *state; + int nwritten; + int sys_errno; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct tstream_roh_writev_state); + nwritten = tstream_writev_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (nwritten == -1) { + tevent_req_error(req, sys_errno); + return; + } + state->nwritten = nwritten; + state->roh_conn->default_channel_in->sent_bytes += nwritten; + + tevent_req_done(req); +} + +static int tstream_roh_writev_recv(struct tevent_req *req, int *perrno) +{ + struct tstream_roh_writev_state *state; + int ret; + + state = tevent_req_data(req, struct tstream_roh_writev_state); + ret = tsocket_simple_int_recv(req, perrno); + if (ret == 0) { + ret = state->nwritten; + } + + return ret; +} + +struct tstream_roh_disconnect_state { + struct tstream_context *stream; + struct tevent_context *ev; +}; + +static void tstream_roh_disconnect_channel_in_handler(struct tevent_req *subreq); +static struct tevent_req * tstream_roh_disconnect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *stream) +{ + struct tstream_roh_context *ctx = NULL; + struct tevent_req *req, *subreq; + struct tstream_roh_disconnect_state *state; + + req = tevent_req_create(mem_ctx, &state, struct tstream_roh_disconnect_state); + if (req == NULL) { + return NULL; + } + + state->stream = stream; + state->ev = ev; + + ctx = tstream_context_data(stream, struct tstream_roh_context); + if (!ctx->roh_conn) { + tevent_req_error(req, ENOTCONN); + goto post; + } + if (!ctx->roh_conn->default_channel_in) { + tevent_req_error(req, ENOTCONN); + goto post; + } + + subreq = http_disconnect_send( + state, + ev, + ctx->roh_conn->default_channel_in->http_conn); + if (tevent_req_nomem(subreq, req)) { + goto post; + } + tevent_req_set_callback(subreq, tstream_roh_disconnect_channel_in_handler, req); + + return req; +post: + tevent_req_post(req, ev); + return req; +} + +static void tstream_roh_disconnect_channel_out_handler(struct tevent_req *subreq); + +static void tstream_roh_disconnect_channel_in_handler(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct tstream_roh_disconnect_state *state; + struct tstream_context *stream; + struct tstream_roh_context *roh_stream; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct tstream_roh_disconnect_state); + stream = state->stream; + roh_stream = tstream_context_data(stream, struct tstream_roh_context); + + ret = http_disconnect_recv(subreq); + TALLOC_FREE(subreq); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + TALLOC_FREE(roh_stream->roh_conn->default_channel_in); + + subreq = http_disconnect_send( + state, + state->ev, + roh_stream->roh_conn->default_channel_out->http_conn); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tstream_roh_disconnect_channel_out_handler, req); + + return; +} + +static void tstream_roh_disconnect_channel_out_handler(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct tstream_roh_disconnect_state *state; + struct tstream_context *stream; + struct tstream_roh_context *roh_stream; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct tstream_roh_disconnect_state); + stream = state->stream; + roh_stream = tstream_context_data(stream, struct tstream_roh_context); + + ret = http_disconnect_recv(subreq); + TALLOC_FREE(subreq); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + TALLOC_FREE(roh_stream->roh_conn->default_channel_out); + + tevent_req_done(req); +} + +static int tstream_roh_disconnect_recv(struct tevent_req *req, int *perrno) +{ + int ret; + + ret = tsocket_simple_int_recv(req, perrno); + tevent_req_received(req); + + return ret; +} diff --git a/source4/librpc/rpc/dcerpc_roh.h b/source4/librpc/rpc/dcerpc_roh.h new file mode 100644 index 0000000..f7a9c75 --- /dev/null +++ b/source4/librpc/rpc/dcerpc_roh.h @@ -0,0 +1,111 @@ +/* + Unix SMB/CIFS implementation. + + [MS-RPCH] - RPC over HTTP + + Copyright (C) 2013 Samuel Cabrero <samuelcabrero@kernevil.me> + + 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/>. +*/ + +#ifndef DCERPC_ROH_H_ +#define DCERPC_ROH_H_ + +#include "librpc/gen_ndr/misc.h" + +struct tevent_queue; +struct tstream_context; +struct tstream_tls_params; + +struct roh_channel { + /* + * The ConnectionTimeout command specifies the desired frequency for + * sending keep-alive PDUs (2.2.3.5.3) + */ + unsigned int connection_timeout; + + unsigned int sent_bytes; + + struct GUID channel_cookie; + + struct http_conn *http_conn; +}; + +enum roh_protocol_version { + ROH_V1, + ROH_V2, +}; + +enum roh_connection_state { + ROH_STATE_OPEN_START, + ROH_STATE_OUT_CHANNEL_WAIT, + ROH_STATE_WAIT_A3W, + ROH_STATE_WAIT_C2, + ROH_STATE_OPENED, +}; + +/* + * protocol_version: A client node should be capable of using v1 and v2, + * try to use v2 in first place. If it fails, fallback + * to v1 + * connection_state: Tracks the protocol current state + * connection_cookie: Identifies the virtual connection among a client, one + * or more inbound proxies, one or more outbound proxies, + * and a server + * association_group_id_cookie: Used by higher layer protocols to link + * multiple virtual connections (2.2.3.1) + * default_channel_in: + * default_channel_out: + * non_default_channel_in: + * non_default_channel_out: + */ +struct roh_connection { + enum roh_protocol_version protocol_version; + enum roh_connection_state connection_state; + + struct GUID connection_cookie; + struct GUID association_group_id_cookie; + + struct roh_channel *default_channel_in; + struct roh_channel *non_default_channel_in; + + struct roh_channel *default_channel_out; + struct roh_channel *non_default_channel_out; + + /* Client role specific fields (3.2.2.1) */ + bool proxy_use; + uint32_t current_keep_alive_time; + uint32_t current_keep_alive_interval; + + /* TODO Add timers 3.2.2.2 */ +}; + +/* Command type constants */ +#define ROH_CMD_TYPE_RECV_WINDOWS_SIZE 0x00000000 /* Section 2.2.3.5.1 */ +#define ROH_CMD_TYPE_FLOW_CONTROL_ACK 0x00000001 /* Section 2.2.3.5.2 */ +#define ROH_CMD_TYPE_CONNECTION_TIMEOUT 0x00000002 /* Section 2.2.3.5.3 */ +#define ROH_CMD_TYPE_COOKIE 0x00000003 /* Section 2.2.3.5.4 */ +#define ROH_CMD_TYPE_CHANNEL_LIFETIME 0x00000004 /* Section 2.2.3.5.5 */ +#define ROH_CMD_TYPE_CLIENT_KEEPALIVE 0x00000005 /* Section 2.2.3.5.6 */ +#define ROH_CMD_TYPE_VERSION 0x00000006 /* Section 2.2.3.5.7 */ +#define ROH_CMD_TYPE_EMPTY 0x00000007 /* Section 2.2.3.5.8 */ +#define ROH_CMD_TYPE_PADDING 0x00000008 /* Section 2.2.3.5.9 */ +#define ROH_CMD_TYPE_NEGATIVE_ANCE 0x00000009 /* Section 2.2.3.5.10 */ +#define ROH_CMD_TYPE_ANCE 0x0000000A /* Section 2.2.3.5.11 */ +#define ROH_CMD_TYPE_CLIENT_ADDRESS 0x0000000B /* Section 2.2.3.5.12 */ +#define ROH_CMD_TYPE_ASSOCIATION_GRP_ID 0x0000000C /* Section 2.2.3.5.13 */ +#define ROH_CMD_TYPE_DESTINATION 0x0000000D /* Section 2.2.3.5.14 */ +#define ROH_CMD_TYPE_PING 0x0000000E /* Section 2.2.3.5.15 */ + +#endif /* DCERPC_ROH_H_ */ diff --git a/source4/librpc/rpc/dcerpc_roh_channel_in.c b/source4/librpc/rpc/dcerpc_roh_channel_in.c new file mode 100644 index 0000000..5010452 --- /dev/null +++ b/source4/librpc/rpc/dcerpc_roh_channel_in.c @@ -0,0 +1,303 @@ +/* + Unix SMB/CIFS implementation. + + [MS-RPCH] - RPC over HTTP client + + Copyright (C) 2013 Samuel Cabrero <samuelcabrero@kernevil.me> + Copyright (C) Julien Kerihuel <j.kerihuel@openchange.org> 2013 + + 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 <tevent.h> +#include <talloc.h> +#include "lib/tsocket/tsocket.h" +#include "lib/tls/tls.h" +#include "lib/util/tevent_ntstatus.h" +#include "lib/util/util_net.h" +#include "libcli/resolve/resolve.h" +#include "libcli/composite/composite.h" +#include "auth/credentials/credentials.h" +#include "auth/credentials/credentials_internal.h" +#include <gen_ndr/dcerpc.h> +#include <gen_ndr/ndr_dcerpc.h> + +#include "librpc/rpc/dcerpc.h" +#include "librpc/rpc/dcerpc_roh.h" +#include "librpc/rpc/dcerpc_proto.h" +#include "libcli/http/http.h" + +struct roh_request_state { + struct http_request *request; + struct http_request *response; +}; + +static void roh_send_RPC_DATA_IN_done(struct tevent_req *subreq); +struct tevent_req *roh_send_RPC_DATA_IN_send(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + struct tevent_context *ev, + struct cli_credentials *credentials, + struct roh_connection *roh, + const char *rpc_server, + uint32_t rpc_server_port, + const char *rpc_proxy, + uint8_t http_auth) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct roh_request_state *state; + const char *path; + char *query; + char *uri; + + DEBUG(8, ("%s: Sending RPC_IN_DATA request\n", __func__)); + + req = tevent_req_create(mem_ctx, &state, struct roh_request_state); + if (req == NULL) { + return NULL; + } + + state->request = talloc_zero(state, struct http_request); + if (tevent_req_nomem(state->request, req)) { + return tevent_req_post(req, ev); + } + + /* Build URI, as specified in section 2.2.2 */ + query = talloc_asprintf(state, "%s:%d", rpc_server, rpc_server_port); + if (tevent_req_nomem(query, req)) { + return tevent_req_post(req, ev); + } + + /* + * TODO This path changes to "/rpcwithcert/rpcproxy.dll" when using + * certificates + */ + path = "/rpc/rpcproxy.dll"; + uri = talloc_asprintf(state, "%s?%s", path, query); + if (tevent_req_nomem(uri, req)) { + tevent_req_nterror(req, NT_STATUS_NO_MEMORY); + return tevent_req_post(req, ev); + } + TALLOC_FREE(query); + + /* + * Create the HTTP channel IN request as specified in the + * section 2.1.2.1.1 + */ + state->request->type = HTTP_REQ_RPC_IN_DATA; + state->request->uri = uri; + state->request->body.length = 0; + state->request->body.data = NULL; + state->request->major = '1'; + state->request->minor = '0'; + + http_add_header(state, &state->request->headers, + "Accept", "application/rpc"); + http_add_header(state, &state->request->headers, + "User-Agent", "MSRPC"); + http_add_header(state, &state->request->headers, + "Host", rpc_proxy); + http_add_header(state, &state->request->headers, + "Connection", "keep-alive"); + http_add_header(state, &state->request->headers, + "Content-Length", "1073741824"); + http_add_header(state, &state->request->headers, + "Cache-Control", "no-cache"); + http_add_header(state, &state->request->headers, + "Pragma", "no-cache"); + + subreq = http_send_auth_request_send(state, + ev, + roh->default_channel_in->http_conn, + state->request, + credentials, + lp_ctx, + http_auth); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, roh_send_RPC_DATA_IN_done, req); + + return req; +} + +static void roh_send_RPC_DATA_IN_done(struct tevent_req *subreq) +{ + NTSTATUS status; + struct tevent_req *req; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + /* Receive the sent bytes to check if request has been properly sent */ + status = http_send_auth_request_recv(subreq); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + DEBUG(8, ("%s: RPC_IN_DATA sent\n", __func__)); + + tevent_req_done(req); +} + +NTSTATUS roh_send_RPC_DATA_IN_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; +} + +struct roh_send_pdu_state { + DATA_BLOB buffer; + struct iovec iov; + int bytes_written; + int sys_errno; +}; + +static void roh_send_CONN_B1_done(struct tevent_req *subreq); +struct tevent_req *roh_send_CONN_B1_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct roh_connection *roh) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct roh_send_pdu_state *state; + struct dcerpc_rts rts; + struct ncacn_packet pkt; + struct ndr_push *ndr; + struct tstream_context *stream = NULL; + struct tevent_queue *send_queue = NULL; + + DEBUG(8, ("%s: Sending CONN/B1 request\n", __func__)); + + req = tevent_req_create(mem_ctx, &state, struct roh_send_pdu_state); + if (req == NULL) { + return NULL; + } + + rts.Flags = RTS_FLAG_NONE; + rts.NumberOfCommands = 6; + rts.Commands = talloc_array(state, struct dcerpc_rts_cmd, 6); + + /* CONN/B1: Version RTS command */ + rts.Commands[0].CommandType = 0x00000006; + rts.Commands[0].Command.Version.Version = 0x00000001; + + /* CONN/B1: VirtualConnectionCookie RTS command */ + rts.Commands[1].CommandType = 0x00000003; + rts.Commands[1].Command.Cookie.Cookie.Cookie = roh->connection_cookie; + + /* CONN/B1: InChannelCookie RTS command */ + rts.Commands[2].CommandType = 0x00000003; + rts.Commands[2].Command.Cookie.Cookie.Cookie = + roh->default_channel_in->channel_cookie; + + /* CONN/B1: ChannelLifetime */ + rts.Commands[3].CommandType = 0x00000004; + rts.Commands[3].Command.ReceiveWindowSize.ReceiveWindowSize = + 0x40000000; + + /* CONN/B1: ClientKeepAlive */ + rts.Commands[4].CommandType = 0x00000005; + rts.Commands[4].Command.ClientKeepalive.ClientKeepalive = 0x000493e0; + + /* CONN/B1: AssociationGroupId */ + rts.Commands[5].CommandType = 0x0000000C; + rts.Commands[5].Command.AssociationGroupId.AssociationGroupId.Cookie = + roh->association_group_id_cookie; + + pkt.rpc_vers = 5; + pkt.rpc_vers_minor = 0; + pkt.ptype = DCERPC_PKT_RTS; + pkt.pfc_flags = DCERPC_PFC_FLAG_LAST | DCERPC_PFC_FLAG_FIRST; + pkt.drep[0] = DCERPC_DREP_LE; + pkt.drep[1] = 0; + pkt.drep[2] = 0; + pkt.drep[3] = 0; + pkt.frag_length = 104; + pkt.auth_length = 0; + pkt.call_id = 0; + pkt.u.rts = rts; + + ndr = ndr_push_init_ctx(state); + if (ndr == NULL) { + return NULL; + } + ndr->offset = 0; + ndr_push_ncacn_packet(ndr, NDR_SCALARS, &pkt); + + state->buffer = ndr_push_blob(ndr); + state->iov.iov_base = (char *) state->buffer.data; + state->iov.iov_len = state->buffer.length; + + stream = http_conn_tstream(roh->default_channel_in->http_conn); + send_queue = http_conn_send_queue(roh->default_channel_in->http_conn); + + subreq = tstream_writev_queue_send(mem_ctx, + ev, + stream, + send_queue, + &state->iov, + 1); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, roh_send_CONN_B1_done, req); + + return req; +} + +static void roh_send_CONN_B1_done(struct tevent_req *subreq) +{ + NTSTATUS status; + struct tevent_req *req; + struct roh_send_pdu_state *state; + int sys_errno; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct roh_send_pdu_state); + + state->bytes_written = tstream_writev_queue_recv(subreq, &sys_errno); + state->sys_errno = sys_errno; + TALLOC_FREE(subreq); + if (state->bytes_written <= 0 && state->sys_errno != 0) { + status = map_nt_error_from_unix_common(sys_errno); + tevent_req_nterror(req, status); + return; + } + DEBUG(8, ("%s: CONN/B1 sent (%d bytes written)\n", + __func__, state->bytes_written)); + + tevent_req_done(req); +} + +NTSTATUS roh_send_CONN_B1_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; +} diff --git a/source4/librpc/rpc/dcerpc_roh_channel_out.c b/source4/librpc/rpc/dcerpc_roh_channel_out.c new file mode 100644 index 0000000..2abafb0 --- /dev/null +++ b/source4/librpc/rpc/dcerpc_roh_channel_out.c @@ -0,0 +1,582 @@ +/* + Unix SMB/CIFS implementation. + + [MS-RPCH] - RPC over HTTP client + + Copyright (C) 2013 Samuel Cabrero <samuelcabrero@kernevil.me> + Copyright (C) Julien Kerihuel <j.kerihuel@openchange.org> 2013 + + 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 <tevent.h> +#include <talloc.h> +#include "lib/tsocket/tsocket.h" +#include "lib/tls/tls.h" +#include "lib/util/tevent_ntstatus.h" +#include "lib/util/util_net.h" +#include "libcli/resolve/resolve.h" +#include "libcli/composite/composite.h" +#include "auth/credentials/credentials.h" +#include "auth/credentials/credentials_internal.h" +#include <gen_ndr/dcerpc.h> +#include <gen_ndr/ndr_dcerpc.h> + +#include "librpc/rpc/dcerpc.h" +#include "librpc/rpc/dcerpc_roh.h" +#include "librpc/rpc/dcerpc_proto.h" +#include "librpc/rpc/dcerpc_util.h" +#include "libcli/http/http.h" + +struct roh_request_state { + struct http_request *request; + struct http_request *response; +}; + +static void roh_send_RPC_DATA_OUT_done(struct tevent_req *subreq); +struct tevent_req *roh_send_RPC_DATA_OUT_send(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + struct tevent_context *ev, + struct cli_credentials *credentials, + struct roh_connection *roh, + const char *rpc_server, + uint32_t rpc_server_port, + const char *rpc_proxy, + uint8_t http_auth) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct roh_request_state *state; + const char *path; + char *query; + char *uri; + + DEBUG(8, ("%s: Sending RPC_OUT_DATA request\n", __func__)); + + req = tevent_req_create(mem_ctx, &state, struct roh_request_state); + if (req == NULL) { + return NULL; + } + + state->request = talloc_zero(state, struct http_request); + if (tevent_req_nomem(state->request, req)) { + return tevent_req_post(req, ev); + } + + /* Build URI, as specified in section 2.2.2 */ + query = talloc_asprintf(state, "%s:%d", rpc_server, rpc_server_port); + if (tevent_req_nomem(query, req)) { + return tevent_req_post(req, ev); + } + + /* + * TODO This path changes to "/rpcwithcert/rpcproxy.dll" when using + * certificates + */ + path = "/rpc/rpcproxy.dll"; + uri = talloc_asprintf(state, "%s?%s", path, query); + if (tevent_req_nomem(uri, req)) { + tevent_req_nterror(req, NT_STATUS_NO_MEMORY); + return tevent_req_post(req, ev); + } + TALLOC_FREE(query); + + /* + * Create the HTTP channel OUT request as specified in the + * section 2.1.2.1.2 + */ + state->request->type = HTTP_REQ_RPC_OUT_DATA; + state->request->uri = uri; + state->request->body.length = 0; + state->request->body.data = NULL; + state->request->major = '1'; + state->request->minor = '0'; + + http_add_header(state, &state->request->headers, + "Accept", "application/rpc"); + http_add_header(state, &state->request->headers, + "User-Agent", "MSRPC"); + http_add_header(state, &state->request->headers, + "Host", rpc_proxy); + http_add_header(state, &state->request->headers, + "Connection", "keep-alive"); + http_add_header(state, &state->request->headers, + "Content-Length", "76"); + http_add_header(state, &state->request->headers, + "Cache-Control", "no-cache"); + http_add_header(state, &state->request->headers, + "Pragma", "no-cache"); + + subreq = http_send_auth_request_send(state, + ev, + roh->default_channel_out->http_conn, + state->request, + credentials, + lp_ctx, + http_auth); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, roh_send_RPC_DATA_OUT_done, req); + + return req; +} + +static void roh_send_RPC_DATA_OUT_done(struct tevent_req *subreq) +{ + NTSTATUS status; + struct tevent_req *req; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + /* Receive the sent bytes to check if request has been properly sent */ + status = http_send_auth_request_recv(subreq); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + DEBUG(8, ("%s: RPC_OUT_DATA sent", __func__)); + + tevent_req_done(req); +} + +NTSTATUS roh_send_RPC_DATA_OUT_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; +} + +struct roh_send_pdu_state { + DATA_BLOB buffer; + struct iovec iov; + int bytes_written; + int sys_errno; +}; + +static void roh_send_CONN_A1_done(struct tevent_req *subreq); +struct tevent_req *roh_send_CONN_A1_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct roh_connection *roh) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct roh_send_pdu_state *state; + struct dcerpc_rts rts; + struct ncacn_packet pkt; + struct ndr_push *ndr; + struct tstream_context *stream = NULL; + struct tevent_queue *send_queue = NULL; + + DEBUG(8, ("%s: Sending CONN/A1 request\n", __func__)); + + req = tevent_req_create(mem_ctx, &state, struct roh_send_pdu_state); + if (req == NULL) { + return NULL; + } + + rts.Flags = RTS_FLAG_NONE; + rts.NumberOfCommands = 4; + rts.Commands = talloc_array(state, struct dcerpc_rts_cmd, 4); + + /* CONN/A1: Version RTS command */ + rts.Commands[0].CommandType = 0x00000006; + rts.Commands[0].Command.Version.Version = 0x00000001; + + /* CONN/A1: VirtualConnectionCookie RTS command */ + rts.Commands[1].CommandType = 0x00000003; + rts.Commands[1].Command.Cookie.Cookie.Cookie = roh->connection_cookie; + + /* CONN/A1: OutChannelCookie RTS command */ + rts.Commands[2].CommandType = 0x00000003; + rts.Commands[2].Command.Cookie.Cookie.Cookie = + roh->default_channel_out->channel_cookie; + + /* CONN/A1: ReceiveWindowSize */ + rts.Commands[3].CommandType = 0x00000000; + rts.Commands[3].Command.ReceiveWindowSize.ReceiveWindowSize = 0x40000; + + pkt.rpc_vers = 5; + pkt.rpc_vers_minor = 0; + pkt.ptype = DCERPC_PKT_RTS; + pkt.pfc_flags = DCERPC_PFC_FLAG_LAST | DCERPC_PFC_FLAG_FIRST; + pkt.drep[0] = DCERPC_DREP_LE; + pkt.drep[1] = 0; + pkt.drep[2] = 0; + pkt.drep[3] = 0; + pkt.frag_length = 76; + pkt.auth_length = 0; + pkt.call_id = 0; + pkt.u.rts = rts; + + ndr = ndr_push_init_ctx(state); + if (ndr == NULL) { + return NULL; + } + ndr->offset = 0; + ndr_push_ncacn_packet(ndr, NDR_SCALARS, &pkt); + + state->buffer = ndr_push_blob(ndr); + state->iov.iov_base = (char *) state->buffer.data; + state->iov.iov_len = state->buffer.length; + + stream = http_conn_tstream(roh->default_channel_out->http_conn); + send_queue = http_conn_send_queue(roh->default_channel_out->http_conn); + + subreq = tstream_writev_queue_send(mem_ctx, + ev, + stream, + send_queue, + &state->iov, + 1); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, roh_send_CONN_A1_done, req); + + return req; +} + +static void roh_send_CONN_A1_done(struct tevent_req *subreq) +{ + NTSTATUS status; + struct tevent_req *req; + struct roh_send_pdu_state *state; + int sys_errno; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct roh_send_pdu_state); + + state->bytes_written = tstream_writev_queue_recv(subreq, &sys_errno); + state->sys_errno = sys_errno; + TALLOC_FREE(subreq); + if (state->bytes_written <= 0 && sys_errno != 0) { + status = map_nt_error_from_unix_common(sys_errno); + tevent_req_nterror(req, status); + return; + } + DEBUG(8, ("%s: CONN/A1 sent (%d bytes written)\n", + __func__, state->bytes_written)); + + tevent_req_done(req); +} + +NTSTATUS roh_send_CONN_A1_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; +} + +struct roh_recv_response_state +{ + struct http_request *response; +}; + +static void roh_recv_out_channel_response_done(struct tevent_req *); +struct tevent_req *roh_recv_out_channel_response_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct roh_connection *roh) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct roh_recv_response_state *state; + + DEBUG(8, ("%s: Waiting for RPC_OUT_DATA response\n", __func__)); + + req = tevent_req_create(mem_ctx, &state, struct roh_recv_response_state); + if (req == NULL) { + return NULL; + } + + subreq = http_read_response_send(state, ev, + roh->default_channel_out->http_conn, + 0); /* we'll get the content later */ + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, roh_recv_out_channel_response_done, req); + + return req; +} + +static void roh_recv_out_channel_response_done(struct tevent_req *subreq) +{ + NTSTATUS status; + struct tevent_req *req; + struct roh_recv_response_state *state; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct roh_recv_response_state); + status = http_read_response_recv(subreq, state, &state->response); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + DEBUG(8, ("%s: RCP_OUT_DATA response received\n", __func__)); + + /* TODO Map response code to nt error */ + switch (state->response->response_code) { + case 200: + break; + case 401: + DEBUG(0, ("%s: Server response: Access denied\n", __func__)); + tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED); + return; + case 503: + /* TODO Decode error info as specified in section 2.1.2.1.3 */ + DEBUG(0, ("%s: Server response: RPC error\n", __func__)); + tevent_req_nterror(req, NT_STATUS_GENERIC_NOT_MAPPED); + return; + default: + DEBUG(0, ("%s: Server response: Unknown error\n", __func__)); + tevent_req_nterror(req, NT_STATUS_GENERIC_NOT_MAPPED); + return; + } + + tevent_req_done(req); +} + +NTSTATUS roh_recv_out_channel_response_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + char **response_msg) +{ + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + tevent_req_received(req); + return NT_STATUS_OK; +} + +struct roh_recv_pdu_state { + struct roh_connection *roh; + uint32_t connection_timeout; + uint32_t version; + uint32_t recv_window_size; +}; + +static void roh_recv_CONN_A3_done(struct tevent_req *subreq); +struct tevent_req *roh_recv_CONN_A3_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct roh_connection *roh) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct roh_recv_pdu_state *state; + struct tstream_context *stream = NULL; + + req = tevent_req_create(mem_ctx, &state, struct roh_recv_pdu_state); + if (req == NULL) { + return NULL; + } + + DEBUG(8, ("%s: Waiting for CONN/A3\n", __func__)); + + stream = http_conn_tstream(roh->default_channel_out->http_conn); + + subreq = dcerpc_read_ncacn_packet_send(state, ev, stream); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, roh_recv_CONN_A3_done, req); + + return req; +} + +static void roh_recv_CONN_A3_done(struct tevent_req *subreq) +{ + NTSTATUS status; + struct tevent_req *req; + struct roh_recv_pdu_state *state; + struct ncacn_packet *pkt; + DATA_BLOB buffer; + struct dcerpc_rts rts; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct roh_recv_pdu_state); + status = dcerpc_read_ncacn_packet_recv(subreq, state, &pkt, &buffer); + TALLOC_FREE(subreq); + + if (tevent_req_nterror(req, status)) { + DEBUG(0, ("%s: Error receiving PDU\n", __func__)); + return; + } + + /* + * Check if it is a CONN/A3 (2.2.4.4) packet and get the connection + * timeout + */ + rts = pkt->u.rts; + if (rts.NumberOfCommands != 1) { + DEBUG(0, ("%s: Invalid number of commands received\n", __func__)); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return; + } + + if (rts.Commands[0].CommandType != ROH_CMD_TYPE_CONNECTION_TIMEOUT) { + DEBUG(0, ("%s: Invalid command type received\n", __func__)); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return; + } + + /* Extract connection timeout */ + state->connection_timeout = rts.Commands[0].Command.ConnectionTimeout.ConnectionTimeout; + + DEBUG(8, ("%s: CONN/A3 received, connection timeout is %u\n", + __func__, state->connection_timeout)); + tevent_req_done(req); +} + +NTSTATUS roh_recv_CONN_A3_recv(struct tevent_req *req, + unsigned int *connection_timeout) +{ + NTSTATUS status; + struct roh_recv_pdu_state *state; + + state = tevent_req_data(req, struct roh_recv_pdu_state); + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *connection_timeout = state->connection_timeout; + + tevent_req_received(req); + return NT_STATUS_OK; +} + +static void roh_recv_CONN_C2_done(struct tevent_req *subreq); +struct tevent_req *roh_recv_CONN_C2_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct roh_connection *roh) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct roh_recv_pdu_state *state; + struct tstream_context *stream = NULL; + + req = tevent_req_create(mem_ctx, &state, struct roh_recv_pdu_state); + if (req == NULL) { + return NULL; + } + + DEBUG(8, ("%s: Waiting for CONN/C2\n", __func__)); + stream = http_conn_tstream(roh->default_channel_out->http_conn); + + subreq = dcerpc_read_ncacn_packet_send(state, ev, stream); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, roh_recv_CONN_C2_done, req); + + return req; +} + +static void roh_recv_CONN_C2_done(struct tevent_req *subreq) +{ + NTSTATUS status; + struct tevent_req *req; + struct roh_recv_pdu_state *state; + struct ncacn_packet *pkt; + DATA_BLOB buffer; + struct dcerpc_rts rts; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct roh_recv_pdu_state); + + status = dcerpc_read_ncacn_packet_recv(subreq, state, &pkt, &buffer); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + DEBUG(0, ("%s: Error receiving PDU\n", __func__)); + return; + } + + /* + * Check if it is a CONN/C2 packet (2.2.4.9), and get the version, the + * receive windows size and the connection timeout for the IN channel + */ + rts = pkt->u.rts; + if (rts.NumberOfCommands != 3) { + DEBUG(0, ("%s: Invalid number of commands received\n", + __func__)); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return; + } + if (rts.Commands[0].CommandType != ROH_CMD_TYPE_VERSION) { + DEBUG(0, ("%s: Invalid command type received\n", __func__)); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return; + } + if (rts.Commands[1].CommandType != ROH_CMD_TYPE_RECV_WINDOWS_SIZE) { + DEBUG(0, ("%s: Invalid command type received\n", __func__)); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return; + } + if (rts.Commands[2].CommandType != ROH_CMD_TYPE_CONNECTION_TIMEOUT) { + DEBUG(0, ("%s: Invalid command type received\n", __func__)); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return; + } + + /* Extract data */ + state->version = rts.Commands[0].Command.Version.Version; + state->recv_window_size = rts.Commands[1].Command.ReceiveWindowSize.ReceiveWindowSize; + state->connection_timeout = rts.Commands[2].Command.ConnectionTimeout.ConnectionTimeout; + + DEBUG(8, ("%s: CONN/C2 received, version is %u, receive windows size is %u, connection timeout is %u\n", + __func__, state->version, state->recv_window_size, + state->connection_timeout)); + tevent_req_done(req); +} + +NTSTATUS roh_recv_CONN_C2_recv(struct tevent_req *req, + unsigned int *version, + unsigned int *recv_window_size, + unsigned int *connection_timeout) +{ + NTSTATUS status; + struct roh_recv_pdu_state *state; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + state = tevent_req_data(req, struct roh_recv_pdu_state); + *version = state->version; + *recv_window_size = state->recv_window_size; + *connection_timeout = state->connection_timeout; + + tevent_req_received(req); + return NT_STATUS_OK; +} diff --git a/source4/librpc/rpc/dcerpc_schannel.c b/source4/librpc/rpc/dcerpc_schannel.c new file mode 100644 index 0000000..68bc3b3 --- /dev/null +++ b/source4/librpc/rpc/dcerpc_schannel.c @@ -0,0 +1,620 @@ +/* + Unix SMB/CIFS implementation. + + dcerpc schannel operations + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + Copyright (C) Rafal Szczesniak 2006 + + 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 <tevent.h> +#include "auth/auth.h" +#include "libcli/composite/composite.h" +#include "libcli/auth/libcli_auth.h" +#include "librpc/gen_ndr/ndr_netlogon.h" +#include "librpc/gen_ndr/ndr_netlogon_c.h" +#include "auth/credentials/credentials.h" +#include "librpc/rpc/dcerpc_proto.h" +#include "param/param.h" +#include "lib/param/loadparm.h" + +struct schannel_key_state { + struct dcerpc_pipe *pipe; + struct dcerpc_pipe *pipe2; + struct dcerpc_binding *binding; + bool dcerpc_schannel_auto; + struct cli_credentials *credentials; + struct netlogon_creds_CredentialState *creds; + uint32_t local_negotiate_flags; + uint32_t remote_negotiate_flags; + struct netr_Credential credentials1; + struct netr_Credential credentials2; + struct netr_Credential credentials3; + struct netr_ServerReqChallenge r; + struct netr_ServerAuthenticate2 a; + const struct samr_Password *mach_pwd; +}; + + +static void continue_secondary_connection(struct composite_context *ctx); +static void continue_bind_auth_none(struct composite_context *ctx); +static void continue_srv_challenge(struct tevent_req *subreq); +static void continue_srv_auth2(struct tevent_req *subreq); +static void continue_get_capabilities(struct tevent_req *subreq); + + +/* + Stage 2 of schannel_key: Receive endpoint mapping and request secondary + rpc connection +*/ +static void continue_epm_map_binding(struct composite_context *ctx) +{ + struct composite_context *c; + struct schannel_key_state *s; + struct composite_context *sec_conn_req; + + c = talloc_get_type(ctx->async.private_data, struct composite_context); + s = talloc_get_type(c->private_data, struct schannel_key_state); + + /* receive endpoint mapping */ + c->status = dcerpc_epm_map_binding_recv(ctx); + if (!NT_STATUS_IS_OK(c->status)) { + DEBUG(0,("Failed to map DCERPC/TCP NCACN_NP pipe for '%s' - %s\n", + NDR_NETLOGON_UUID, nt_errstr(c->status))); + composite_error(c, c->status); + return; + } + + /* send a request for secondary rpc connection */ + sec_conn_req = dcerpc_secondary_connection_send(s->pipe, + s->binding); + if (composite_nomem(sec_conn_req, c)) return; + + composite_continue(c, sec_conn_req, continue_secondary_connection, c); +} + + +/* + Stage 3 of schannel_key: Receive secondary rpc connection and perform + non-authenticated bind request +*/ +static void continue_secondary_connection(struct composite_context *ctx) +{ + struct composite_context *c; + struct schannel_key_state *s; + struct composite_context *auth_none_req; + + c = talloc_get_type(ctx->async.private_data, struct composite_context); + s = talloc_get_type(c->private_data, struct schannel_key_state); + + /* receive secondary rpc connection */ + c->status = dcerpc_secondary_connection_recv(ctx, &s->pipe2); + if (!composite_is_ok(c)) return; + + talloc_steal(s, s->pipe2); + + /* initiate a non-authenticated bind */ + auth_none_req = dcerpc_bind_auth_none_send(c, s->pipe2, &ndr_table_netlogon); + if (composite_nomem(auth_none_req, c)) return; + + composite_continue(c, auth_none_req, continue_bind_auth_none, c); +} + + +/* + Stage 4 of schannel_key: Receive non-authenticated bind and get + a netlogon challenge +*/ +static void continue_bind_auth_none(struct composite_context *ctx) +{ + struct composite_context *c; + struct schannel_key_state *s; + struct tevent_req *subreq; + + c = talloc_get_type(ctx->async.private_data, struct composite_context); + s = talloc_get_type(c->private_data, struct schannel_key_state); + + /* receive result of non-authenticated bind request */ + c->status = dcerpc_bind_auth_none_recv(ctx); + if (!composite_is_ok(c)) return; + + /* prepare a challenge request */ + s->r.in.server_name = talloc_asprintf(c, "\\\\%s", dcerpc_server_name(s->pipe)); + if (composite_nomem(s->r.in.server_name, c)) return; + s->r.in.computer_name = cli_credentials_get_workstation(s->credentials); + s->r.in.credentials = &s->credentials1; + s->r.out.return_credentials = &s->credentials2; + + generate_random_buffer(s->credentials1.data, sizeof(s->credentials1.data)); + + /* + request a netlogon challenge - a rpc request over opened secondary pipe + */ + subreq = dcerpc_netr_ServerReqChallenge_r_send(s, c->event_ctx, + s->pipe2->binding_handle, + &s->r); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_srv_challenge, c); +} + + +/* + Stage 5 of schannel_key: Receive a challenge and perform authentication + on the netlogon pipe +*/ +static void continue_srv_challenge(struct tevent_req *subreq) +{ + struct composite_context *c; + struct schannel_key_state *s; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type(c->private_data, struct schannel_key_state); + + /* receive rpc request result - netlogon challenge */ + c->status = dcerpc_netr_ServerReqChallenge_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + /* prepare credentials for auth2 request */ + s->mach_pwd = cli_credentials_get_nt_hash(s->credentials, c); + + /* auth2 request arguments */ + s->a.in.server_name = s->r.in.server_name; + s->a.in.account_name = cli_credentials_get_username(s->credentials); + s->a.in.secure_channel_type = + cli_credentials_get_secure_channel_type(s->credentials); + s->a.in.computer_name = cli_credentials_get_workstation(s->credentials); + s->a.in.negotiate_flags = &s->local_negotiate_flags; + s->a.in.credentials = &s->credentials3; + s->a.out.negotiate_flags = &s->remote_negotiate_flags; + s->a.out.return_credentials = &s->credentials3; + + s->creds = netlogon_creds_client_init(s, + s->a.in.account_name, + s->a.in.computer_name, + s->a.in.secure_channel_type, + &s->credentials1, &s->credentials2, + s->mach_pwd, &s->credentials3, + s->local_negotiate_flags); + if (composite_nomem(s->creds, c)) { + return; + } + /* + authenticate on the netlogon pipe - a rpc request over secondary pipe + */ + subreq = dcerpc_netr_ServerAuthenticate2_r_send(s, c->event_ctx, + s->pipe2->binding_handle, + &s->a); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_srv_auth2, c); +} + + +/* + Stage 6 of schannel_key: Receive authentication request result and verify + received credentials +*/ +static void continue_srv_auth2(struct tevent_req *subreq) +{ + struct composite_context *c; + struct schannel_key_state *s; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type(c->private_data, struct schannel_key_state); + + /* receive rpc request result - auth2 credentials */ + c->status = dcerpc_netr_ServerAuthenticate2_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + if (!NT_STATUS_EQUAL(s->a.out.result, NT_STATUS_ACCESS_DENIED) && + !NT_STATUS_IS_OK(s->a.out.result)) { + composite_error(c, s->a.out.result); + return; + } + + /* + * Strong keys could be unsupported (NT4) or disables. So retry with the + * flags returned by the server. - asn + */ + if (NT_STATUS_EQUAL(s->a.out.result, NT_STATUS_ACCESS_DENIED)) { + uint32_t lf = s->local_negotiate_flags; + const char *ln = NULL; + uint32_t rf = s->remote_negotiate_flags; + const char *rn = NULL; + + if (!s->dcerpc_schannel_auto) { + composite_error(c, s->a.out.result); + return; + } + s->dcerpc_schannel_auto = false; + + if (lf & NETLOGON_NEG_SUPPORTS_AES) { + ln = "aes"; + if (rf & NETLOGON_NEG_SUPPORTS_AES) { + composite_error(c, s->a.out.result); + return; + } + } else if (lf & NETLOGON_NEG_STRONG_KEYS) { + ln = "strong"; + if (rf & NETLOGON_NEG_STRONG_KEYS) { + composite_error(c, s->a.out.result); + return; + } + } else { + ln = "des"; + } + + if (rf & NETLOGON_NEG_SUPPORTS_AES) { + rn = "aes"; + } else if (rf & NETLOGON_NEG_STRONG_KEYS) { + rn = "strong"; + } else { + rn = "des"; + } + + DEBUG(3, ("Server doesn't support %s keys, downgrade to %s" + "and retry! local[0x%08X] remote[0x%08X]\n", + ln, rn, lf, rf)); + + s->local_negotiate_flags = s->remote_negotiate_flags; + + generate_random_buffer(s->credentials1.data, + sizeof(s->credentials1.data)); + + subreq = dcerpc_netr_ServerReqChallenge_r_send(s, + c->event_ctx, + s->pipe2->binding_handle, + &s->r); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_srv_challenge, c); + return; + } + + s->creds->negotiate_flags = s->remote_negotiate_flags; + + /* verify credentials */ + if (!netlogon_creds_client_check(s->creds, s->a.out.return_credentials)) { + composite_error(c, NT_STATUS_UNSUCCESSFUL); + return; + } + + composite_done(c); +} + +/* + Initiate establishing a schannel key using netlogon challenge + on a secondary pipe +*/ +static struct composite_context *dcerpc_schannel_key_send(TALLOC_CTX *mem_ctx, + struct dcerpc_pipe *p, + struct cli_credentials *credentials, + struct loadparm_context *lp_ctx) +{ + struct composite_context *c; + struct schannel_key_state *s; + struct composite_context *epm_map_req; + enum netr_SchannelType schannel_type = cli_credentials_get_secure_channel_type(credentials); + struct cli_credentials *epm_creds = NULL; + + /* composite context allocation and setup */ + c = composite_create(mem_ctx, p->conn->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct schannel_key_state); + if (composite_nomem(s, c)) return c; + c->private_data = s; + + /* store parameters in the state structure */ + s->pipe = p; + s->credentials = credentials; + s->local_negotiate_flags = NETLOGON_NEG_AUTH2_FLAGS; + + /* allocate credentials */ + if (s->pipe->conn->flags & DCERPC_SCHANNEL_128) { + s->local_negotiate_flags = NETLOGON_NEG_AUTH2_ADS_FLAGS; + } + if (s->pipe->conn->flags & DCERPC_SCHANNEL_AES) { + s->local_negotiate_flags = NETLOGON_NEG_AUTH2_ADS_FLAGS; + s->local_negotiate_flags |= NETLOGON_NEG_SUPPORTS_AES; + } + if (s->pipe->conn->flags & DCERPC_SCHANNEL_AUTO) { + s->local_negotiate_flags = NETLOGON_NEG_AUTH2_ADS_FLAGS; + s->local_negotiate_flags |= NETLOGON_NEG_SUPPORTS_AES; + s->dcerpc_schannel_auto = true; + } + + /* type of authentication depends on schannel type */ + if (schannel_type == SEC_CHAN_RODC) { + s->local_negotiate_flags |= NETLOGON_NEG_RODC_PASSTHROUGH; + } + + if (lpcfg_weak_crypto(lp_ctx) == SAMBA_WEAK_CRYPTO_DISALLOWED) { + s->local_negotiate_flags &= ~NETLOGON_NEG_ARCFOUR; + } + + epm_creds = cli_credentials_init_anon(s); + if (composite_nomem(epm_creds, c)) return c; + + /* allocate binding structure */ + s->binding = dcerpc_binding_dup(s, s->pipe->binding); + if (composite_nomem(s->binding, c)) return c; + + /* request the netlogon endpoint mapping */ + epm_map_req = dcerpc_epm_map_binding_send(c, s->binding, + &ndr_table_netlogon, + epm_creds, + s->pipe->conn->event_ctx, + lp_ctx); + if (composite_nomem(epm_map_req, c)) return c; + + composite_continue(c, epm_map_req, continue_epm_map_binding, c); + return c; +} + + +/* + Receive result of schannel key request + */ +static NTSTATUS dcerpc_schannel_key_recv(struct composite_context *c, + TALLOC_CTX *mem_ctx, + struct netlogon_creds_CredentialState **creds) +{ + NTSTATUS status = composite_wait(c); + + if (NT_STATUS_IS_OK(status)) { + struct schannel_key_state *s = + talloc_get_type_abort(c->private_data, + struct schannel_key_state); + *creds = talloc_move(mem_ctx, &s->creds); + } + + talloc_free(c); + return status; +} + + +struct auth_schannel_state { + struct dcerpc_pipe *pipe; + struct cli_credentials *credentials; + const struct ndr_interface_table *table; + struct loadparm_context *lp_ctx; + uint8_t auth_level; + struct netlogon_creds_CredentialState *creds_state; + struct netlogon_creds_CredentialState save_creds_state; + struct netr_Authenticator auth; + struct netr_Authenticator return_auth; + union netr_Capabilities capabilities; + struct netr_LogonGetCapabilities c; +}; + + +static void continue_bind_auth(struct composite_context *ctx); + + +/* + Stage 2 of auth_schannel: Receive schannel key and intitiate an + authenticated bind using received credentials + */ +static void continue_schannel_key(struct composite_context *ctx) +{ + struct composite_context *auth_req; + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + struct auth_schannel_state *s = talloc_get_type(c->private_data, + struct auth_schannel_state); + NTSTATUS status; + + /* receive schannel key */ + status = c->status = dcerpc_schannel_key_recv(ctx, s, &s->creds_state); + if (!composite_is_ok(c)) { + DEBUG(1, ("Failed to setup credentials: %s\n", nt_errstr(status))); + return; + } + + /* send bind auth request with received creds */ + cli_credentials_set_netlogon_creds(s->credentials, s->creds_state); + + auth_req = dcerpc_bind_auth_send(c, s->pipe, s->table, s->credentials, + lpcfg_gensec_settings(c, s->lp_ctx), + DCERPC_AUTH_TYPE_SCHANNEL, s->auth_level, + NULL); + if (composite_nomem(auth_req, c)) return; + + composite_continue(c, auth_req, continue_bind_auth, c); +} + + +/* + Stage 3 of auth_schannel: Receivce result of authenticated bind + and say if we're done ok. +*/ +static void continue_bind_auth(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + struct auth_schannel_state *s = talloc_get_type(c->private_data, + struct auth_schannel_state); + struct tevent_req *subreq; + + c->status = dcerpc_bind_auth_recv(ctx); + if (!composite_is_ok(c)) return; + + /* if we have a AES encrypted connection, verify the capabilities */ + if (ndr_syntax_id_equal(&s->table->syntax_id, + &ndr_table_netlogon.syntax_id)) { + NTSTATUS status; + ZERO_STRUCT(s->return_auth); + + s->save_creds_state = *s->creds_state; + status = netlogon_creds_client_authenticator(&s->save_creds_state, + &s->auth); + if (!NT_STATUS_IS_OK(status)) { + composite_error(c, status); + return; + } + + s->c.in.server_name = talloc_asprintf(c, + "\\\\%s", + dcerpc_server_name(s->pipe)); + if (composite_nomem(s->c.in.server_name, c)) return; + s->c.in.computer_name = cli_credentials_get_workstation(s->credentials); + s->c.in.credential = &s->auth; + s->c.in.return_authenticator = &s->return_auth; + s->c.in.query_level = 1; + + s->c.out.capabilities = &s->capabilities; + s->c.out.return_authenticator = &s->return_auth; + + DEBUG(5, ("We established a AES connection, verifying logon " + "capabilities\n")); + + subreq = dcerpc_netr_LogonGetCapabilities_r_send(s, + c->event_ctx, + s->pipe->binding_handle, + &s->c); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_get_capabilities, c); + return; + } + + composite_done(c); +} + +/* + Stage 4 of auth_schannel: Get the Logon Capablities and verify them. +*/ +static void continue_get_capabilities(struct tevent_req *subreq) +{ + struct composite_context *c; + struct auth_schannel_state *s; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type(c->private_data, struct auth_schannel_state); + + /* receive rpc request result */ + c->status = dcerpc_netr_LogonGetCapabilities_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (NT_STATUS_EQUAL(c->status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) { + if (s->creds_state->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + composite_error(c, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } else { + /* This is probably NT */ + composite_done(c); + return; + } + } else if (!composite_is_ok(c)) { + return; + } + + if (NT_STATUS_EQUAL(s->c.out.result, NT_STATUS_NOT_IMPLEMENTED)) { + if (s->creds_state->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + /* This means AES isn't supported. */ + composite_error(c, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + /* This is probably an old Samba version */ + composite_done(c); + return; + } + + /* verify credentials */ + if (!netlogon_creds_client_check(&s->save_creds_state, + &s->c.out.return_authenticator->cred)) { + composite_error(c, NT_STATUS_UNSUCCESSFUL); + return; + } + + *s->creds_state = s->save_creds_state; + cli_credentials_set_netlogon_creds(s->credentials, s->creds_state); + + if (!NT_STATUS_IS_OK(s->c.out.result)) { + composite_error(c, s->c.out.result); + return; + } + + /* compare capabilities */ + if (s->creds_state->negotiate_flags != s->capabilities.server_capabilities) { + DEBUG(2, ("The client capabilities don't match the server " + "capabilities: local[0x%08X] remote[0x%08X]\n", + s->creds_state->negotiate_flags, + s->capabilities.server_capabilities)); + composite_error(c, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + /* TODO: Add downgrade dectection. */ + + composite_done(c); +} + + +/* + Initiate schannel authentication request +*/ +struct composite_context *dcerpc_bind_auth_schannel_send(TALLOC_CTX *tmp_ctx, + struct dcerpc_pipe *p, + const struct ndr_interface_table *table, + struct cli_credentials *credentials, + struct loadparm_context *lp_ctx, + uint8_t auth_level) +{ + struct composite_context *c; + struct auth_schannel_state *s; + struct composite_context *schan_key_req; + + /* composite context allocation and setup */ + c = composite_create(tmp_ctx, p->conn->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct auth_schannel_state); + if (composite_nomem(s, c)) return c; + c->private_data = s; + + /* store parameters in the state structure */ + s->pipe = p; + s->credentials = credentials; + s->table = table; + s->auth_level = auth_level; + s->lp_ctx = lp_ctx; + + /* start getting schannel key first */ + schan_key_req = dcerpc_schannel_key_send(c, p, credentials, lp_ctx); + if (composite_nomem(schan_key_req, c)) return c; + + composite_continue(c, schan_key_req, continue_schannel_key, c); + return c; +} + + +/* + Receive result of schannel authentication request +*/ +NTSTATUS dcerpc_bind_auth_schannel_recv(struct composite_context *c) +{ + NTSTATUS status = composite_wait(c); + + talloc_free(c); + return status; +} diff --git a/source4/librpc/rpc/dcerpc_secondary.c b/source4/librpc/rpc/dcerpc_secondary.c new file mode 100644 index 0000000..55068dc --- /dev/null +++ b/source4/librpc/rpc/dcerpc_secondary.c @@ -0,0 +1,431 @@ +/* + Unix SMB/CIFS implementation. + + dcerpc connect functions + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Jelmer Vernooij 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2007 + Copyright (C) Rafal Szczesniak 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 "libcli/composite/composite.h" +#include "lib/events/events.h" +#include "librpc/rpc/dcerpc.h" +#include "librpc/rpc/dcerpc_proto.h" +#include "auth/credentials/credentials.h" +#include "param/param.h" +#include "libcli/resolve/resolve.h" +#include "lib/util/util_net.h" + +struct sec_conn_state { + struct dcerpc_pipe *pipe; + struct dcerpc_pipe *pipe2; + struct dcerpc_binding *binding; +}; + + +static void continue_open_smb(struct composite_context *ctx); +static void continue_open_tcp(struct composite_context *ctx); +static void continue_open_ncalrpc(struct composite_context *ctx); +static void continue_open_ncacn_unix(struct composite_context *ctx); +static void continue_pipe_open(struct composite_context *c); + + +/* + Send request to create a secondary dcerpc connection from a primary + connection +*/ +_PUBLIC_ struct composite_context* dcerpc_secondary_connection_send(struct dcerpc_pipe *p, + const struct dcerpc_binding *b) +{ + struct composite_context *c; + struct sec_conn_state *s; + struct composite_context *pipe_smb_req; + struct composite_context *pipe_tcp_req; + const char *localaddress = NULL; + struct composite_context *pipe_ncalrpc_req; + const char *ncalrpc_dir = NULL; + struct composite_context *pipe_unix_req; + const char *host; + const char *target_hostname; + const char *endpoint; + + /* composite context allocation and setup */ + c = composite_create(p, p->conn->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct sec_conn_state); + if (composite_nomem(s, c)) return c; + c->private_data = s; + + s->pipe = p; + s->binding = dcerpc_binding_dup(s, b); + if (composite_nomem(s->binding, c)) return c; + + /* initialise second dcerpc pipe based on primary pipe's event context */ + s->pipe2 = dcerpc_pipe_init(c, s->pipe->conn->event_ctx); + if (composite_nomem(s->pipe2, c)) return c; + + if (DEBUGLEVEL >= 10) + s->pipe2->conn->packet_log_dir = s->pipe->conn->packet_log_dir; + + host = dcerpc_binding_get_string_option(s->binding, "host"); + if (host == NULL) { + /* + * We may fallback to the host of the given connection + */ + host = dcerpc_binding_get_string_option(s->pipe->binding, + "host"); + } + target_hostname = dcerpc_binding_get_string_option(s->binding, "target_hostname"); + if (target_hostname == NULL) { + /* + * We may fallback to the target_hostname of the given connection + */ + target_hostname = dcerpc_binding_get_string_option(s->pipe->binding, + "target_hostname"); + } + endpoint = dcerpc_binding_get_string_option(s->binding, "endpoint"); + if (endpoint == NULL) { + /* + * We may fallback to the endpoint of the given connection + */ + endpoint = dcerpc_binding_get_string_option(s->pipe->binding, "endpoint"); + } + if (endpoint == NULL) { + composite_error(c, NT_STATUS_INVALID_PARAMETER_MIX); + return c; + } + + /* open second dcerpc pipe using the same transport as for primary pipe */ + switch (s->pipe->conn->transport.transport) { + case NCACN_NP: + pipe_smb_req = dcerpc_secondary_smb_send(s->pipe->conn, + s->pipe2->conn, + endpoint); + composite_continue(c, pipe_smb_req, continue_open_smb, c); + return c; + + case NCACN_IP_TCP: + if (host == NULL) { + composite_error(c, NT_STATUS_INVALID_PARAMETER_MIX); + return c; + } + + if (!is_ipaddress(host)) { + /* + * We may fallback to the host of the given connection + */ + host = dcerpc_binding_get_string_option(s->pipe->binding, + "host"); + if (host == NULL) { + composite_error(c, NT_STATUS_INVALID_PARAMETER_MIX); + return c; + } + if (!is_ipaddress(host)) { + composite_error(c, NT_STATUS_INVALID_PARAMETER_MIX); + return c; + } + } + + localaddress = dcerpc_binding_get_string_option(s->binding, + "localaddress"); + if (localaddress == NULL) { + /* + * We may fallback to the localaddress of the given connection + */ + localaddress = dcerpc_binding_get_string_option(s->pipe->binding, + "localaddress"); + } + + pipe_tcp_req = dcerpc_pipe_open_tcp_send(s->pipe2->conn, + localaddress, + host, + target_hostname, + atoi(endpoint), + resolve_context_init(s)); + composite_continue(c, pipe_tcp_req, continue_open_tcp, c); + return c; + + case NCALRPC: + ncalrpc_dir = dcerpc_binding_get_string_option(s->binding, + "ncalrpc_dir"); + if (ncalrpc_dir == NULL) { + ncalrpc_dir = dcerpc_binding_get_string_option(s->pipe->binding, + "ncalrpc_dir"); + } + if (ncalrpc_dir == NULL) { + composite_error(c, NT_STATUS_INVALID_PARAMETER_MIX); + return c; + } + + pipe_ncalrpc_req = dcerpc_pipe_open_pipe_send(s->pipe2->conn, + ncalrpc_dir, + endpoint); + composite_continue(c, pipe_ncalrpc_req, continue_open_ncalrpc, c); + return c; + + case NCACN_UNIX_STREAM: + pipe_unix_req = dcerpc_pipe_open_unix_stream_send(s->pipe2->conn, + endpoint); + composite_continue(c, pipe_unix_req, continue_open_ncacn_unix, c); + return c; + + default: + /* looks like a transport we don't support */ + composite_error(c, NT_STATUS_NOT_SUPPORTED); + } + + return c; +} + + +/* + Stage 2 of secondary_connection: Receive result of pipe open request on smb +*/ +static void continue_open_smb(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + + c->status = dcerpc_secondary_smb_recv(ctx); + if (!composite_is_ok(c)) return; + + continue_pipe_open(c); +} + + +/* + Stage 2 of secondary_connection: Receive result of pipe open request on tcp/ip +*/ +static void continue_open_tcp(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + struct sec_conn_state *s = talloc_get_type_abort(c->private_data, + struct sec_conn_state); + char *localaddr = NULL; + char *remoteaddr = NULL; + + c->status = dcerpc_pipe_open_tcp_recv(ctx, s, &localaddr, &remoteaddr); + if (!composite_is_ok(c)) return; + + c->status = dcerpc_binding_set_string_option(s->binding, + "localaddress", + localaddr); + if (!composite_is_ok(c)) return; + + c->status = dcerpc_binding_set_string_option(s->binding, + "host", + remoteaddr); + if (!composite_is_ok(c)) return; + + continue_pipe_open(c); +} + +/* + Stage 2 of secondary_connection: Receive result of pipe open request on ncalrpc +*/ +static void continue_open_ncalrpc(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + + c->status = dcerpc_pipe_open_pipe_recv(ctx); + if (!composite_is_ok(c)) return; + + continue_pipe_open(c); +} + +/* + Stage 2 of secondary_connection: Receive result of pipe open request on ncacn_unix +*/ +static void continue_open_ncacn_unix(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + + c->status = dcerpc_pipe_open_unix_stream_recv(ctx); + if (!composite_is_ok(c)) return; + + continue_pipe_open(c); +} + + +/* + Stage 3 of secondary_connection: Get binding data and flags from primary pipe + and say if we're done ok. +*/ +static void continue_pipe_open(struct composite_context *c) +{ + struct sec_conn_state *s; + + s = talloc_get_type(c->private_data, struct sec_conn_state); + + s->pipe2->conn->flags = s->pipe->conn->flags; + s->pipe2->binding = dcerpc_binding_dup(s->pipe2, s->binding); + if (composite_nomem(s->pipe2->binding, c)) { + return; + } + + composite_done(c); +} + + +/* + Receive result of secondary rpc connection request and return + second dcerpc pipe. +*/ +_PUBLIC_ NTSTATUS dcerpc_secondary_connection_recv(struct composite_context *c, + struct dcerpc_pipe **p2) +{ + NTSTATUS status = composite_wait(c); + struct sec_conn_state *s; + + s = talloc_get_type(c->private_data, struct sec_conn_state); + + if (NT_STATUS_IS_OK(status)) { + *p2 = talloc_steal(s->pipe, s->pipe2); + } + + talloc_free(c); + return status; +} + +/* + Create a secondary DCERPC connection, then bind (and possibly + authenticate) using the supplied credentials. + + This creates a second connection, to the same host (and on ncacn_np on the same connection) as the first +*/ +struct sec_auth_conn_state { + struct dcerpc_pipe *pipe2; + const struct dcerpc_binding *binding; + const struct ndr_interface_table *table; + struct cli_credentials *credentials; + struct composite_context *ctx; + struct loadparm_context *lp_ctx; +}; + +static void dcerpc_secondary_auth_connection_bind(struct composite_context *ctx); +static void dcerpc_secondary_auth_connection_continue(struct composite_context *ctx); + +_PUBLIC_ struct composite_context* dcerpc_secondary_auth_connection_send(struct dcerpc_pipe *p, + const struct dcerpc_binding *binding, + const struct ndr_interface_table *table, + struct cli_credentials *credentials, + struct loadparm_context *lp_ctx) +{ + + struct composite_context *c, *secondary_conn_ctx; + struct sec_auth_conn_state *s; + + /* composite context allocation and setup */ + c = composite_create(p, p->conn->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct sec_auth_conn_state); + if (composite_nomem(s, c)) return c; + c->private_data = s; + s->ctx = c; + + s->binding = binding; + s->table = table; + s->credentials = credentials; + s->lp_ctx = lp_ctx; + + secondary_conn_ctx = dcerpc_secondary_connection_send(p, binding); + + if (composite_nomem(secondary_conn_ctx, s->ctx)) { + talloc_free(c); + return NULL; + } + + composite_continue(s->ctx, secondary_conn_ctx, dcerpc_secondary_auth_connection_bind, + s); + return c; +} + +/* + Stage 2 of secondary_auth_connection: + Having made the secondary connection, we will need to do an (authenticated) bind +*/ +static void dcerpc_secondary_auth_connection_bind(struct composite_context *ctx) +{ + struct composite_context *secondary_auth_ctx; + struct sec_auth_conn_state *s = talloc_get_type(ctx->async.private_data, + struct sec_auth_conn_state); + + s->ctx->status = dcerpc_secondary_connection_recv(ctx, &s->pipe2); + if (!composite_is_ok(s->ctx)) return; + + secondary_auth_ctx = dcerpc_pipe_auth_send(s->pipe2, s->binding, s->table, s->credentials, + s->lp_ctx); + composite_continue(s->ctx, secondary_auth_ctx, dcerpc_secondary_auth_connection_continue, s); + +} + +/* + Stage 3 of secondary_auth_connection: Receive result of authenticated bind request +*/ +static void dcerpc_secondary_auth_connection_continue(struct composite_context *ctx) +{ + struct sec_auth_conn_state *s = talloc_get_type(ctx->async.private_data, + struct sec_auth_conn_state); + + s->ctx->status = dcerpc_pipe_auth_recv(ctx, s, &s->pipe2); + if (!composite_is_ok(s->ctx)) return; + + composite_done(s->ctx); +} + +/* + Receive an authenticated pipe, created as a secondary connection +*/ +_PUBLIC_ NTSTATUS dcerpc_secondary_auth_connection_recv(struct composite_context *c, + TALLOC_CTX *mem_ctx, + struct dcerpc_pipe **p) +{ + NTSTATUS status = composite_wait(c); + struct sec_auth_conn_state *s; + + s = talloc_get_type(c->private_data, struct sec_auth_conn_state); + + if (NT_STATUS_IS_OK(status)) { + *p = talloc_steal(mem_ctx, s->pipe2); + } + + talloc_free(c); + return status; +} + +_PUBLIC_ NTSTATUS dcerpc_secondary_auth_connection(struct dcerpc_pipe *p, + const struct dcerpc_binding *binding, + const struct ndr_interface_table *table, + struct cli_credentials *credentials, + struct loadparm_context *lp_ctx, + TALLOC_CTX *mem_ctx, + struct dcerpc_pipe **p2) +{ + struct composite_context *c; + + c = dcerpc_secondary_auth_connection_send(p, binding, table, + credentials, lp_ctx); + return dcerpc_secondary_auth_connection_recv(c, mem_ctx, p2); +} diff --git a/source4/librpc/rpc/dcerpc_smb.c b/source4/librpc/rpc/dcerpc_smb.c new file mode 100644 index 0000000..259de71 --- /dev/null +++ b/source4/librpc/rpc/dcerpc_smb.c @@ -0,0 +1,310 @@ +/* + Unix SMB/CIFS implementation. + + dcerpc over SMB transport + + Copyright (C) Tim Potter 2003 + Copyright (C) Andrew Tridgell 2003 + + 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 <tevent.h> +#include "lib/tsocket/tsocket.h" +#include "libcli/smb/smb_constants.h" +#include "libcli/smb/smbXcli_base.h" +#include "libcli/smb/tstream_smbXcli_np.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/smb2/smb2.h" +#include "librpc/rpc/dcerpc.h" +#include "librpc/rpc/dcerpc_proto.h" +#include "libcli/composite/composite.h" + +#undef strncasecmp + +/* transport private information used by SMB pipe transport */ +struct smb_private { + DATA_BLOB session_key; + + /* + * these are needed to open a secondary connection + */ + struct smbXcli_conn *conn; + struct smbXcli_session *session; + struct smbXcli_tcon *tcon; + uint32_t timeout_msec; +}; + +/* + fetch the user session key +*/ +static NTSTATUS smb_session_key(struct dcecli_connection *c, DATA_BLOB *session_key) +{ + struct smb_private *smb = talloc_get_type_abort( + c->transport.private_data, struct smb_private); + + if (smb == NULL) return NT_STATUS_CONNECTION_DISCONNECTED; + + if (smb->session_key.length == 0) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + + *session_key = smb->session_key; + return NT_STATUS_OK; +} + +struct dcerpc_pipe_open_smb_state { + struct dcecli_connection *c; + struct composite_context *ctx; + + const char *fname; + + struct smb_private *smb; +}; + +static void dcerpc_pipe_open_smb_done(struct tevent_req *subreq); + +struct composite_context *dcerpc_pipe_open_smb_send(struct dcecli_connection *c, + struct smbXcli_conn *conn, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint32_t timeout_msec, + const char *pipe_name) +{ + struct composite_context *ctx; + struct dcerpc_pipe_open_smb_state *state; + uint16_t pid = 0; + struct tevent_req *subreq; + + ctx = composite_create(c, c->event_ctx); + if (ctx == NULL) return NULL; + + state = talloc(ctx, struct dcerpc_pipe_open_smb_state); + if (composite_nomem(state, ctx)) return ctx; + ctx->private_data = state; + + state->c = c; + state->ctx = ctx; + + if ((strncasecmp(pipe_name, "/pipe/", 6) == 0) || + (strncasecmp(pipe_name, "\\pipe\\", 6) == 0)) { + pipe_name += 6; + } + if ((strncasecmp(pipe_name, "/", 1) == 0) || + (strncasecmp(pipe_name, "\\", 1) == 0)) { + pipe_name += 1; + } + state->fname = talloc_strdup(state, pipe_name); + if (composite_nomem(state->fname, ctx)) return ctx; + + state->smb = talloc_zero(state, struct smb_private); + if (composite_nomem(state->smb, ctx)) return ctx; + + state->smb->conn = conn; + state->smb->session = session; + state->smb->tcon = tcon; + state->smb->timeout_msec = timeout_msec; + + state->c->server_name = strupper_talloc(state->c, + smbXcli_conn_remote_name(conn)); + if (composite_nomem(state->c->server_name, ctx)) return ctx; + + ctx->status = smbXcli_session_application_key(session, + state->smb, + &state->smb->session_key); + if (NT_STATUS_EQUAL(ctx->status, NT_STATUS_NO_USER_SESSION_KEY)) { + state->smb->session_key = data_blob_null; + ctx->status = NT_STATUS_OK; + } + if (!composite_is_ok(ctx)) return ctx; + + subreq = tstream_smbXcli_np_open_send(state, c->event_ctx, + conn, session, tcon, pid, + timeout_msec, state->fname); + if (composite_nomem(subreq, ctx)) return ctx; + tevent_req_set_callback(subreq, dcerpc_pipe_open_smb_done, state); + + return ctx; +} + +static void dcerpc_pipe_open_smb_done(struct tevent_req *subreq) +{ + struct dcerpc_pipe_open_smb_state *state = + tevent_req_callback_data(subreq, + struct dcerpc_pipe_open_smb_state); + struct composite_context *ctx = state->ctx; + struct dcecli_connection *c = state->c; + uint16_t enc_cipher; + + ctx->status = tstream_smbXcli_np_open_recv(subreq, + state->smb, + &state->c->transport.stream); + TALLOC_FREE(subreq); + if (!composite_is_ok(ctx)) return; + + state->c->transport.write_queue = + tevent_queue_create(state->c, "dcerpc_smb write queue"); + if (composite_nomem(state->c->transport.write_queue, ctx)) return; + + /* + fill in the transport methods + */ + c->transport.transport = NCACN_NP; + c->transport.private_data = NULL; + + /* + * Windows uses 4280 for ncacn_np, + * so we also use it, this is what our + * tstream_smbXcli_np code relies on. + */ + c->srv_max_xmit_frag = 4280; + c->srv_max_recv_frag = 4280; + + /* Over-ride the default session key with the SMB session key */ + c->security_state.session_key = smb_session_key; + + enc_cipher = smb2cli_session_get_encryption_cipher(state->smb->session); + switch (enc_cipher) { + case SMB2_ENCRYPTION_AES128_CCM: + case SMB2_ENCRYPTION_AES128_GCM: + c->transport.encrypted = true; + break; + default: + c->transport.encrypted = false; + } + + c->transport.private_data = talloc_move(c, &state->smb); + + composite_done(ctx); +} + +NTSTATUS dcerpc_pipe_open_smb_recv(struct composite_context *c) +{ + NTSTATUS status = composite_wait(c); + talloc_free(c); + return status; +} + +_PUBLIC_ NTSTATUS dcerpc_pipe_open_smb(struct dcerpc_pipe *p, + struct smbcli_tree *t, + const char *pipe_name) +{ + struct smbXcli_conn *conn; + struct smbXcli_session *session; + struct smbXcli_tcon *tcon; + struct composite_context *ctx; + + conn = t->session->transport->conn; + session = t->session->smbXcli; + tcon = t->smbXcli; + smb1cli_tcon_set_id(tcon, t->tid); + + /* if we don't have a binding on this pipe yet, then create one */ + if (p->binding == NULL) { + struct dcerpc_binding *b; + NTSTATUS status; + const char *r = smbXcli_conn_remote_name(conn); + char *str; + SMB_ASSERT(r != NULL); + str = talloc_asprintf(p, "ncacn_np:%s", r); + if (str == NULL) { + return NT_STATUS_NO_MEMORY; + } + status = dcerpc_parse_binding(p, str, &b); + talloc_free(str); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + p->binding = b; + } + + ctx = dcerpc_pipe_open_smb_send(p->conn, + conn, session, tcon, + DCERPC_REQUEST_TIMEOUT * 1000, + pipe_name); + if (ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + return dcerpc_pipe_open_smb_recv(ctx); +} + +_PUBLIC_ NTSTATUS dcerpc_pipe_open_smb2(struct dcerpc_pipe *p, + struct smb2_tree *t, + const char *pipe_name) +{ + struct smbXcli_conn *conn; + struct smbXcli_session *session; + struct smbXcli_tcon *tcon; + struct composite_context *ctx; + + conn = t->session->transport->conn; + session = t->session->smbXcli; + tcon = t->smbXcli; + + /* if we don't have a binding on this pipe yet, then create one */ + if (p->binding == NULL) { + struct dcerpc_binding *b; + NTSTATUS status; + const char *r = smbXcli_conn_remote_name(conn); + char *str; + SMB_ASSERT(r != NULL); + str = talloc_asprintf(p, "ncacn_np:%s", r); + if (str == NULL) { + return NT_STATUS_NO_MEMORY; + } + status = dcerpc_parse_binding(p, str, &b); + talloc_free(str); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + p->binding = b; + } + + ctx = dcerpc_pipe_open_smb_send(p->conn, + conn, session, tcon, + DCERPC_REQUEST_TIMEOUT * 1000, + pipe_name); + if (ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + return dcerpc_pipe_open_smb_recv(ctx); +} + +struct composite_context *dcerpc_secondary_smb_send(struct dcecli_connection *c1, + struct dcecli_connection *c2, + const char *pipe_name) +{ + struct smb_private *smb; + + if (c1->transport.transport != NCACN_NP) return NULL; + + smb = talloc_get_type(c1->transport.private_data, struct smb_private); + if (!smb) return NULL; + + return dcerpc_pipe_open_smb_send(c2, + smb->conn, + smb->session, + smb->tcon, + smb->timeout_msec, + pipe_name); +} + +NTSTATUS dcerpc_secondary_smb_recv(struct composite_context *c) +{ + return dcerpc_pipe_open_smb_recv(c); +} diff --git a/source4/librpc/rpc/dcerpc_sock.c b/source4/librpc/rpc/dcerpc_sock.c new file mode 100644 index 0000000..ec5a5ca --- /dev/null +++ b/source4/librpc/rpc/dcerpc_sock.c @@ -0,0 +1,499 @@ +/* + Unix SMB/CIFS implementation. + + dcerpc over standard sockets transport + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Jelmer Vernooij 2004 + Copyright (C) Rafal Szczesniak 2006 + + 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/events/events.h" +#include "lib/socket/socket.h" +#include "lib/tsocket/tsocket.h" +#include "libcli/composite/composite.h" +#include "librpc/rpc/dcerpc.h" +#include "librpc/rpc/dcerpc_proto.h" +#include "libcli/resolve/resolve.h" +#include "librpc/rpc/rpc_common.h" + +struct pipe_open_socket_state { + struct dcecli_connection *conn; + struct socket_context *socket_ctx; + struct socket_address *localaddr; + struct socket_address *server; + const char *target_hostname; + enum dcerpc_transport_t transport; + struct socket_address *client; +}; + + +static void continue_socket_connect(struct composite_context *ctx) +{ + struct dcecli_connection *conn; + struct composite_context *c = talloc_get_type_abort( + ctx->async.private_data, struct composite_context); + struct pipe_open_socket_state *s = talloc_get_type_abort( + c->private_data, struct pipe_open_socket_state); + int rc; + int sock_fd; + + /* make it easier to write a function calls */ + conn = s->conn; + + c->status = socket_connect_recv(ctx); + if (!NT_STATUS_IS_OK(c->status)) { + DBG_NOTICE("Failed to connect host %s on port %d - %s\n", + s->server->addr, s->server->port, + nt_errstr(c->status)); + composite_error(c, c->status); + return; + } + + s->client = socket_get_my_addr(s->socket_ctx, s); + if (s->client == NULL) { + TALLOC_FREE(s->socket_ctx); + composite_error(c, NT_STATUS_NO_MEMORY); + return; + } + sock_fd = socket_get_fd(s->socket_ctx); + if (sock_fd == -1) { + TALLOC_FREE(s->socket_ctx); + composite_error(c, NT_STATUS_INVALID_HANDLE); + return; + } + socket_set_flags(s->socket_ctx, SOCKET_FLAG_NOCLOSE); + TALLOC_FREE(s->socket_ctx); + + /* + fill in the transport methods + */ + conn->transport.transport = s->transport; + conn->transport.private_data = NULL; + + /* + * Windows uses 5840 for ncacn_ip_tcp, + * so we also use it (for every transport which uses bsd sockets) + */ + conn->srv_max_xmit_frag = 5840; + conn->srv_max_recv_frag = 5840; + + conn->transport.pending_reads = 0; + conn->server_name = strupper_talloc(conn, s->target_hostname); + + rc = tstream_bsd_existing_socket(conn, sock_fd, + &conn->transport.stream); + if (rc < 0) { + close(sock_fd); + composite_error(c, NT_STATUS_NO_MEMORY); + return; + } + + conn->transport.write_queue = + tevent_queue_create(conn, "dcerpc sock write queue"); + if (conn->transport.write_queue == NULL) { + TALLOC_FREE(conn->transport.stream); + composite_error(c, NT_STATUS_NO_MEMORY); + return; + } + + /* ensure we don't get SIGPIPE */ + BlockSignals(true, SIGPIPE); + + composite_done(c); +} + + +static struct composite_context *dcerpc_pipe_open_socket_send(TALLOC_CTX *mem_ctx, + struct dcecli_connection *cn, + struct socket_address *localaddr, + struct socket_address *server, + const char *target_hostname, + const char *full_path, + enum dcerpc_transport_t transport) +{ + struct composite_context *c; + struct pipe_open_socket_state *s; + struct composite_context *conn_req; + + c = composite_create(mem_ctx, cn->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct pipe_open_socket_state); + if (composite_nomem(s, c)) return c; + c->private_data = s; + + s->conn = cn; + s->transport = transport; + if (localaddr) { + s->localaddr = socket_address_copy(s, localaddr); + if (composite_nomem(s->localaddr, c)) return c; + } + s->server = socket_address_copy(s, server); + if (composite_nomem(s->server, c)) return c; + if (target_hostname) { + s->target_hostname = talloc_strdup(s, target_hostname); + if (composite_nomem(s->target_hostname, c)) return c; + } + + c->status = socket_create(s, server->family, SOCKET_TYPE_STREAM, + &s->socket_ctx, 0); + if (!composite_is_ok(c)) return c; + + conn_req = socket_connect_send(s->socket_ctx, s->localaddr, s->server, 0, + c->event_ctx); + composite_continue(c, conn_req, continue_socket_connect, c); + return c; +} + +static NTSTATUS dcerpc_pipe_open_socket_recv(struct composite_context *c, + TALLOC_CTX *mem_ctx, + struct socket_address **localaddr) +{ + NTSTATUS status = composite_wait(c); + + if (NT_STATUS_IS_OK(status)) { + struct pipe_open_socket_state *s = + talloc_get_type_abort(c->private_data, + struct pipe_open_socket_state); + + if (localaddr != NULL) { + *localaddr = talloc_move(mem_ctx, &s->client); + } + } + + talloc_free(c); + return status; +} + +struct pipe_tcp_state { + const char *server; + const char *target_hostname; + const char **addresses; + uint32_t index; + uint32_t port; + struct socket_address *localaddr; + struct socket_address *srvaddr; + struct resolve_context *resolve_ctx; + struct dcecli_connection *conn; + struct nbt_name name; + char *local_address; + char *remote_address; +}; + + +static void continue_ip_open_socket(struct composite_context *ctx); +static void continue_ip_resolve_name(struct composite_context *ctx); + +static void continue_ip_resolve_name(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type_abort( + ctx->async.private_data, struct composite_context); + struct pipe_tcp_state *s = talloc_get_type_abort( + c->private_data, struct pipe_tcp_state); + struct composite_context *sock_ip_req; + + c->status = resolve_name_multiple_recv(ctx, s, &s->addresses); + if (!composite_is_ok(c)) return; + + /* prepare server address using host ip:port and transport name */ + s->srvaddr = socket_address_from_strings(s->conn, "ip", s->addresses[s->index], s->port); + s->index++; + if (composite_nomem(s->srvaddr, c)) return; + + sock_ip_req = dcerpc_pipe_open_socket_send(c, s->conn, s->localaddr, + s->srvaddr, s->target_hostname, + NULL, + NCACN_IP_TCP); + composite_continue(c, sock_ip_req, continue_ip_open_socket, c); +} + + +/* + Stage 2 of dcerpc_pipe_open_tcp_send: receive result of pipe open request + on IP transport. +*/ +static void continue_ip_open_socket(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type_abort( + ctx->async.private_data, struct composite_context); + struct pipe_tcp_state *s = talloc_get_type_abort( + c->private_data, struct pipe_tcp_state); + struct socket_address *localaddr = NULL; + + /* receive result socket open request */ + c->status = dcerpc_pipe_open_socket_recv(ctx, s, &localaddr); + if (!NT_STATUS_IS_OK(c->status)) { + /* something went wrong... */ + DBG_NOTICE("Failed to connect host %s (%s) on port %d - %s.\n", + s->addresses[s->index - 1], s->target_hostname, + s->port, nt_errstr(c->status)); + if (s->addresses[s->index]) { + struct composite_context *sock_ip_req; + talloc_free(s->srvaddr); + /* prepare server address using host ip:port and transport name */ + s->srvaddr = socket_address_from_strings(s->conn, "ip", s->addresses[s->index], s->port); + s->index++; + if (composite_nomem(s->srvaddr, c)) return; + + sock_ip_req = dcerpc_pipe_open_socket_send(c, s->conn, s->localaddr, + s->srvaddr, s->target_hostname, + NULL, + NCACN_IP_TCP); + composite_continue(c, sock_ip_req, continue_ip_open_socket, c); + + return; + } else { + composite_error(c, c->status); + return; + } + } + + s->local_address = talloc_strdup(s, localaddr->addr); + if (composite_nomem(s->local_address, c)) return; + s->remote_address = talloc_strdup(s, s->addresses[s->index - 1]); + if (composite_nomem(s->remote_address, c)) return; + + composite_done(c); +} + +/* + Send rpc pipe open request to given host:port using + tcp/ip transport +*/ +struct composite_context* dcerpc_pipe_open_tcp_send(struct dcecli_connection *conn, + const char *localaddr, + const char *server, + const char *target_hostname, + uint32_t port, + struct resolve_context *resolve_ctx) +{ + struct composite_context *c; + struct pipe_tcp_state *s; + struct composite_context *resolve_req; + + /* composite context allocation and setup */ + c = composite_create(conn, conn->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct pipe_tcp_state); + if (composite_nomem(s, c)) return c; + c->private_data = s; + + /* store input parameters in state structure */ + s->server = talloc_strdup(c, server); + if (composite_nomem(s->server, c)) return c; + if (target_hostname) { + s->target_hostname = talloc_strdup(c, target_hostname); + if (composite_nomem(s->target_hostname, c)) return c; + } + s->port = port; + s->conn = conn; + s->resolve_ctx = resolve_ctx; + if (localaddr) { + s->localaddr = socket_address_from_strings(s, "ip", localaddr, 0); + /* if there is no localaddr, we pass NULL for + s->localaddr, which is handled by the socket libraries as + meaning no local binding address specified */ + } + + make_nbt_name_server(&s->name, s->server); + resolve_req = resolve_name_send(resolve_ctx, s, &s->name, c->event_ctx); + composite_continue(c, resolve_req, continue_ip_resolve_name, c); + return c; +} + +/* + Receive result of pipe open request on tcp/ip +*/ +NTSTATUS dcerpc_pipe_open_tcp_recv(struct composite_context *c, + TALLOC_CTX *mem_ctx, + char **localaddr, + char **remoteaddr) +{ + NTSTATUS status; + status = composite_wait(c); + + if (NT_STATUS_IS_OK(status)) { + struct pipe_tcp_state *s = talloc_get_type_abort( + c->private_data, struct pipe_tcp_state); + + if (localaddr != NULL) { + *localaddr = talloc_move(mem_ctx, &s->local_address); + } + if (remoteaddr != NULL) { + *remoteaddr = talloc_move(mem_ctx, &s->remote_address); + } + } + + talloc_free(c); + return status; +} + + +struct pipe_unix_state { + const char *path; + struct socket_address *srvaddr; + struct dcecli_connection *conn; +}; + + +/* + Stage 2 of dcerpc_pipe_open_unix_stream_send: receive result of pipe open + request on unix socket. +*/ +static void continue_unix_open_socket(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type_abort( + ctx->async.private_data, struct composite_context); + + c->status = dcerpc_pipe_open_socket_recv(ctx, NULL, NULL); + if (NT_STATUS_IS_OK(c->status)) { + composite_done(c); + return; + } + + composite_error(c, c->status); +} + + +/* + Send pipe open request on unix socket +*/ +struct composite_context *dcerpc_pipe_open_unix_stream_send(struct dcecli_connection *conn, + const char *path) +{ + struct composite_context *c; + struct composite_context *sock_unix_req; + struct pipe_unix_state *s; + + /* composite context allocation and setup */ + c = composite_create(conn, conn->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct pipe_unix_state); + if (composite_nomem(s, c)) return c; + c->private_data = s; + + /* store parameters in state structure */ + s->path = talloc_strdup(c, path); + if (composite_nomem(s->path, c)) return c; + s->conn = conn; + + /* prepare server address using socket path and transport name */ + s->srvaddr = socket_address_from_strings(conn, "unix", s->path, 0); + if (composite_nomem(s->srvaddr, c)) return c; + + /* send socket open request */ + sock_unix_req = dcerpc_pipe_open_socket_send(c, s->conn, NULL, + s->srvaddr, NULL, + s->path, + NCALRPC); + composite_continue(c, sock_unix_req, continue_unix_open_socket, c); + return c; +} + + +/* + Receive result of pipe open request on unix socket +*/ +NTSTATUS dcerpc_pipe_open_unix_stream_recv(struct composite_context *c) +{ + NTSTATUS status = composite_wait(c); + + talloc_free(c); + return status; +} + + +/* + Stage 2 of dcerpc_pipe_open_pipe_send: receive socket open request +*/ +static void continue_np_open_socket(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type_abort( + ctx->async.private_data, struct composite_context); + + c->status = dcerpc_pipe_open_socket_recv(ctx, NULL, NULL); + if (!composite_is_ok(c)) return; + + composite_done(c); +} + + +/* + Send pipe open request on ncalrpc +*/ +struct composite_context* dcerpc_pipe_open_pipe_send(struct dcecli_connection *conn, + const char *ncalrpc_dir, + const char *identifier) +{ + char *canon = NULL; + + struct composite_context *c; + struct composite_context *sock_np_req; + struct pipe_unix_state *s; + + /* composite context allocation and setup */ + c = composite_create(conn, conn->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct pipe_unix_state); + if (composite_nomem(s, c)) return c; + c->private_data = s; + + /* store parameters in state structure */ + canon = talloc_strdup(s, identifier); + if (composite_nomem(canon, c)) return c; + s->conn = conn; + + string_replace(canon, '/', '\\'); + s->path = talloc_asprintf(canon, "%s/%s", ncalrpc_dir, canon); + if (composite_nomem(s->path, c)) return c; + + /* prepare server address using path and transport name */ + s->srvaddr = socket_address_from_strings(conn, "unix", s->path, 0); + if (composite_nomem(s->srvaddr, c)) return c; + + /* send socket open request */ + sock_np_req = dcerpc_pipe_open_socket_send(c, s->conn, NULL, s->srvaddr, NULL, s->path, NCALRPC); + composite_continue(c, sock_np_req, continue_np_open_socket, c); + return c; +} + + +/* + Receive result of pipe open request on ncalrpc +*/ +NTSTATUS dcerpc_pipe_open_pipe_recv(struct composite_context *c) +{ + NTSTATUS status = composite_wait(c); + + talloc_free(c); + return status; +} + + +/* + Open a rpc pipe on a named pipe - sync version +*/ +NTSTATUS dcerpc_pipe_open_pipe(struct dcecli_connection *conn, const char *ncalrpc_dir, const char *identifier) +{ + struct composite_context *c = dcerpc_pipe_open_pipe_send(conn, ncalrpc_dir, identifier); + return dcerpc_pipe_open_pipe_recv(c); +} diff --git a/source4/librpc/rpc/dcerpc_util.c b/source4/librpc/rpc/dcerpc_util.c new file mode 100644 index 0000000..6ea27a8 --- /dev/null +++ b/source4/librpc/rpc/dcerpc_util.c @@ -0,0 +1,811 @@ +/* + Unix SMB/CIFS implementation. + + dcerpc utility functions + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Jelmer Vernooij 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005 + Copyright (C) Rafal Szczesniak 2006 + + 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 "lib/events/events.h" +#include "libcli/composite/composite.h" +#include "librpc/gen_ndr/ndr_epmapper_c.h" +#include "librpc/gen_ndr/ndr_dcerpc.h" +#include "librpc/gen_ndr/ndr_misc.h" +#include "librpc/rpc/dcerpc_proto.h" +#include "auth/credentials/credentials.h" +#include "auth/gensec/gensec.h" +#include "param/param.h" +#include "librpc/rpc/rpc_common.h" + +/* + find a dcerpc call on an interface by name +*/ +const struct ndr_interface_call *dcerpc_iface_find_call(const struct ndr_interface_table *iface, + const char *name) +{ + int i; + for (i=0;i<iface->num_calls;i++) { + if (strcmp(iface->calls[i].name, name) == 0) { + return &iface->calls[i]; + } + } + return NULL; +} + +struct epm_map_binding_state { + struct dcerpc_binding *binding; + const struct ndr_interface_table *table; + struct dcerpc_pipe *pipe; + struct policy_handle handle; + struct GUID object; + struct epm_twr_t twr; + struct epm_twr_t *twr_r; + uint32_t num_towers; + struct epm_Map r; +}; + + +static void continue_epm_recv_binding(struct composite_context *ctx); +static void continue_epm_map(struct tevent_req *subreq); + + +/* + Stage 2 of epm_map_binding: Receive connected rpc pipe and send endpoint + mapping rpc request +*/ +static void continue_epm_recv_binding(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + struct epm_map_binding_state *s = talloc_get_type(c->private_data, + struct epm_map_binding_state); + struct tevent_req *subreq; + + /* receive result of rpc pipe connect request */ + c->status = dcerpc_pipe_connect_b_recv(ctx, c, &s->pipe); + if (!composite_is_ok(c)) return; + + c->status = dcerpc_binding_build_tower(s->pipe, s->binding, &s->twr.tower); + if (!composite_is_ok(c)) return; + + /* with some nice pretty paper around it of course */ + s->r.in.object = &s->object; + s->r.in.map_tower = &s->twr; + s->r.in.entry_handle = &s->handle; + s->r.in.max_towers = 1; + s->r.out.entry_handle = &s->handle; + s->r.out.num_towers = &s->num_towers; + + /* send request for an endpoint mapping - a rpc request on connected pipe */ + subreq = dcerpc_epm_Map_r_send(s, c->event_ctx, + s->pipe->binding_handle, + &s->r); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_epm_map, c); +} + + +/* + Stage 3 of epm_map_binding: Receive endpoint mapping and provide binding details +*/ +static void continue_epm_map(struct tevent_req *subreq) +{ + struct composite_context *c = tevent_req_callback_data(subreq, + struct composite_context); + struct epm_map_binding_state *s = talloc_get_type(c->private_data, + struct epm_map_binding_state); + const char *endpoint; + + /* receive result of a rpc request */ + c->status = dcerpc_epm_Map_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + /* check the details */ + if (s->r.out.result != 0 || *s->r.out.num_towers != 1) { + composite_error(c, NT_STATUS_PORT_UNREACHABLE); + return; + } + + s->twr_r = s->r.out.towers[0].twr; + if (s->twr_r == NULL) { + composite_error(c, NT_STATUS_PORT_UNREACHABLE); + return; + } + + if (s->twr_r->tower.num_floors != s->twr.tower.num_floors || + s->twr_r->tower.floors[3].lhs.protocol != s->twr.tower.floors[3].lhs.protocol) { + composite_error(c, NT_STATUS_PORT_UNREACHABLE); + return; + } + + /* get received endpoint */ + endpoint = dcerpc_floor_get_rhs_data(s, &s->twr_r->tower.floors[3]); + if (composite_nomem(endpoint, c)) return; + + c->status = dcerpc_binding_set_string_option(s->binding, + "endpoint", + endpoint); + if (!composite_is_ok(c)) { + return; + } + + composite_done(c); +} + + +/* + Request for endpoint mapping of dcerpc binding - try to request for endpoint + unless there is default one. +*/ +struct composite_context *dcerpc_epm_map_binding_send(TALLOC_CTX *mem_ctx, + struct dcerpc_binding *binding, + const struct ndr_interface_table *table, + struct cli_credentials *creds, + struct tevent_context *ev, + struct loadparm_context *lp_ctx) +{ + struct composite_context *c; + struct epm_map_binding_state *s; + struct composite_context *pipe_connect_req; + NTSTATUS status; + struct dcerpc_binding *epmapper_binding; + int i; + + if (ev == NULL) { + return NULL; + } + + /* composite context allocation and setup */ + c = composite_create(mem_ctx, ev); + if (c == NULL) { + return NULL; + } + + s = talloc_zero(c, struct epm_map_binding_state); + if (composite_nomem(s, c)) return c; + c->private_data = s; + + s->binding = binding; + s->object = dcerpc_binding_get_object(binding); + s->table = table; + + c->status = dcerpc_binding_set_abstract_syntax(binding, + &table->syntax_id); + if (!composite_is_ok(c)) { + return c; + } + + /* + First, check if there is a default endpoint specified in the IDL + */ + for (i = 0; i < table->endpoints->count; i++) { + struct dcerpc_binding *default_binding; + enum dcerpc_transport_t transport; + enum dcerpc_transport_t dtransport; + const char *dendpoint = NULL; + + status = dcerpc_parse_binding(s, + table->endpoints->names[i], + &default_binding); + if (!NT_STATUS_IS_OK(status)) { + continue; + } + + transport = dcerpc_binding_get_transport(binding); + dtransport = dcerpc_binding_get_transport(default_binding); + if (transport == NCA_UNKNOWN) { + c->status = dcerpc_binding_set_transport(binding, + dtransport); + if (!composite_is_ok(c)) { + return c; + } + transport = dtransport; + } + + if (transport != dtransport) { + TALLOC_FREE(default_binding); + continue; + } + + dendpoint = dcerpc_binding_get_string_option(default_binding, + "endpoint"); + if (dendpoint == NULL) { + TALLOC_FREE(default_binding); + continue; + } + + c->status = dcerpc_binding_set_string_option(binding, + "endpoint", + dendpoint); + if (!composite_is_ok(c)) { + return c; + } + + TALLOC_FREE(default_binding); + composite_done(c); + return c; + } + + epmapper_binding = dcerpc_binding_dup(s, binding); + if (composite_nomem(epmapper_binding, c)) return c; + + /* basic endpoint mapping data */ + c->status = dcerpc_binding_set_string_option(epmapper_binding, + "endpoint", NULL); + if (!composite_is_ok(c)) { + return c; + } + c->status = dcerpc_binding_set_flags(epmapper_binding, 0, UINT32_MAX); + if (!composite_is_ok(c)) { + return c; + } + c->status = dcerpc_binding_set_assoc_group_id(epmapper_binding, 0); + if (!composite_is_ok(c)) { + return c; + } + c->status = dcerpc_binding_set_object(epmapper_binding, GUID_zero()); + if (!composite_is_ok(c)) { + return c; + } + + /* initiate rpc pipe connection */ + pipe_connect_req = dcerpc_pipe_connect_b_send(s, epmapper_binding, + &ndr_table_epmapper, + creds, c->event_ctx, + lp_ctx); + if (composite_nomem(pipe_connect_req, c)) return c; + + composite_continue(c, pipe_connect_req, continue_epm_recv_binding, c); + return c; +} + + +/* + Receive result of endpoint mapping request + */ +NTSTATUS dcerpc_epm_map_binding_recv(struct composite_context *c) +{ + NTSTATUS status = composite_wait(c); + + talloc_free(c); + return status; +} + + +/* + Get endpoint mapping for rpc connection +*/ +_PUBLIC_ NTSTATUS dcerpc_epm_map_binding(TALLOC_CTX *mem_ctx, struct dcerpc_binding *binding, + const struct ndr_interface_table *table, struct tevent_context *ev, + struct loadparm_context *lp_ctx) +{ + struct composite_context *c; + struct cli_credentials *epm_creds; + + epm_creds = cli_credentials_init_anon(mem_ctx); + if (epm_creds == NULL) { + return NT_STATUS_NO_MEMORY; + } + c = dcerpc_epm_map_binding_send(mem_ctx, binding, table, epm_creds, ev, lp_ctx); + if (c == NULL) { + talloc_free(epm_creds); + return NT_STATUS_NO_MEMORY; + } + talloc_steal(c, epm_creds); + return dcerpc_epm_map_binding_recv(c); +} + + +struct pipe_auth_state { + struct dcerpc_pipe *pipe; + const struct dcerpc_binding *binding; + const struct ndr_interface_table *table; + struct loadparm_context *lp_ctx; + struct cli_credentials *credentials; + unsigned int logon_retries; +}; + + +static void continue_auth_schannel(struct composite_context *ctx); +static void continue_auth(struct composite_context *ctx); +static void continue_auth_none(struct composite_context *ctx); +static void continue_ntlmssp_connection(struct composite_context *ctx); +static void continue_spnego_after_wrong_pass(struct composite_context *ctx); + + +/* + Stage 2 of pipe_auth: Receive result of schannel bind request +*/ +static void continue_auth_schannel(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + + c->status = dcerpc_bind_auth_schannel_recv(ctx); + if (!composite_is_ok(c)) return; + + composite_done(c); +} + + +/* + Stage 2 of pipe_auth: Receive result of authenticated bind request +*/ +static void continue_auth(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + + c->status = dcerpc_bind_auth_recv(ctx); + if (!composite_is_ok(c)) return; + + composite_done(c); +} +/* + Stage 2 of pipe_auth: Receive result of authenticated bind request, but handle fallbacks: + SPNEGO -> NTLMSSP +*/ +static void continue_auth_auto(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + struct pipe_auth_state *s = talloc_get_type(c->private_data, struct pipe_auth_state); + struct composite_context *sec_conn_req; + + c->status = dcerpc_bind_auth_recv(ctx); + if (NT_STATUS_EQUAL(c->status, NT_STATUS_INVALID_PARAMETER)) { + /* + * Retry with NTLMSSP auth as fallback + * send a request for secondary rpc connection + */ + sec_conn_req = dcerpc_secondary_connection_send(s->pipe, + s->binding); + composite_continue(c, sec_conn_req, continue_ntlmssp_connection, c); + return; + } else if (NT_STATUS_EQUAL(c->status, NT_STATUS_LOGON_FAILURE) || + NT_STATUS_EQUAL(c->status, NT_STATUS_UNSUCCESSFUL)) { + /* + try a second time on any error. We don't just do it + on LOGON_FAILURE as some servers will give a + NT_STATUS_UNSUCCESSFUL on a authentication error on RPC + */ + const char *principal; + const char *endpoint; + + principal = gensec_get_target_principal(s->pipe->conn->security_state.generic_state); + if (principal == NULL) { + const char *hostname = gensec_get_target_hostname(s->pipe->conn->security_state.generic_state); + const char *service = gensec_get_target_service(s->pipe->conn->security_state.generic_state); + if (hostname != NULL && service != NULL) { + principal = talloc_asprintf(c, "%s/%s", service, hostname); + } + } + + endpoint = dcerpc_binding_get_string_option(s->binding, "endpoint"); + + if ((cli_credentials_failed_kerberos_login(s->credentials, principal, &s->logon_retries) || + cli_credentials_wrong_password(s->credentials)) && + endpoint != NULL) { + /* + * Retry SPNEGO with a better password + * send a request for secondary rpc connection + */ + sec_conn_req = dcerpc_secondary_connection_send(s->pipe, + s->binding); + composite_continue(c, sec_conn_req, continue_spnego_after_wrong_pass, c); + return; + } + } + + if (!composite_is_ok(c)) return; + + composite_done(c); +} + +/* + Stage 3 of pipe_auth (fallback to NTLMSSP case): Receive secondary + rpc connection (the first one can't be used any more, due to the + bind nak) and perform authenticated bind request +*/ +static void continue_ntlmssp_connection(struct composite_context *ctx) +{ + struct composite_context *c; + struct pipe_auth_state *s; + struct composite_context *auth_req; + struct dcerpc_pipe *p2; + void *pp; + + c = talloc_get_type(ctx->async.private_data, struct composite_context); + s = talloc_get_type(c->private_data, struct pipe_auth_state); + + /* receive secondary rpc connection */ + c->status = dcerpc_secondary_connection_recv(ctx, &p2); + if (!composite_is_ok(c)) return; + + + /* this is a rather strange situation. When + we come into the routine, s is a child of s->pipe, and + when we created p2 above, it also became a child of + s->pipe. + + Now we want p2 to be a parent of s->pipe, and we want s to + be a parent of both of them! If we don't do this very + carefully we end up creating a talloc loop + */ + + /* we need the new contexts to hang off the same context + that s->pipe is on, but the only way to get that is + via talloc_parent() */ + pp = talloc_parent(s->pipe); + + /* promote s to be at the top */ + talloc_steal(pp, s); + + /* and put p2 under s */ + talloc_steal(s, p2); + + /* now put s->pipe under p2 */ + talloc_steal(p2, s->pipe); + + s->pipe = p2; + + /* initiate a authenticated bind */ + auth_req = dcerpc_bind_auth_send(c, s->pipe, s->table, + s->credentials, + lpcfg_gensec_settings(c, s->lp_ctx), + DCERPC_AUTH_TYPE_NTLMSSP, + dcerpc_auth_level(s->pipe->conn), + s->table->authservices->names[0]); + composite_continue(c, auth_req, continue_auth, c); +} + +/* + Stage 3 of pipe_auth (retry on wrong password): Receive secondary + rpc connection (the first one can't be used any more, due to the + bind nak) and perform authenticated bind request +*/ +static void continue_spnego_after_wrong_pass(struct composite_context *ctx) +{ + struct composite_context *c; + struct pipe_auth_state *s; + struct composite_context *auth_req; + struct dcerpc_pipe *p2; + + c = talloc_get_type(ctx->async.private_data, struct composite_context); + s = talloc_get_type(c->private_data, struct pipe_auth_state); + + /* receive secondary rpc connection */ + c->status = dcerpc_secondary_connection_recv(ctx, &p2); + if (!composite_is_ok(c)) return; + + talloc_steal(s, p2); + talloc_steal(p2, s->pipe); + s->pipe = p2; + + /* initiate a authenticated bind */ + auth_req = dcerpc_bind_auth_send(c, s->pipe, s->table, + s->credentials, + lpcfg_gensec_settings(c, s->lp_ctx), + DCERPC_AUTH_TYPE_SPNEGO, + dcerpc_auth_level(s->pipe->conn), + s->table->authservices->names[0]); + composite_continue(c, auth_req, continue_auth, c); +} + + +/* + Stage 2 of pipe_auth: Receive result of non-authenticated bind request +*/ +static void continue_auth_none(struct composite_context *ctx) +{ + struct composite_context *c = talloc_get_type(ctx->async.private_data, + struct composite_context); + + c->status = dcerpc_bind_auth_none_recv(ctx); + if (!composite_is_ok(c)) return; + + composite_done(c); +} + + +/* + Request to perform an authenticated bind if required. Authentication + is determined using credentials passed and binding flags. +*/ +struct composite_context *dcerpc_pipe_auth_send(struct dcerpc_pipe *p, + const struct dcerpc_binding *binding, + const struct ndr_interface_table *table, + struct cli_credentials *credentials, + struct loadparm_context *lp_ctx) +{ + struct composite_context *c; + struct pipe_auth_state *s; + struct composite_context *auth_schannel_req; + struct composite_context *auth_req; + struct composite_context *auth_none_req; + struct dcecli_connection *conn; + uint8_t auth_type; + + /* composite context allocation and setup */ + c = composite_create(p, p->conn->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct pipe_auth_state); + if (composite_nomem(s, c)) return c; + c->private_data = s; + + /* store parameters in state structure */ + s->binding = binding; + s->table = table; + s->credentials = credentials; + s->pipe = p; + s->lp_ctx = lp_ctx; + + conn = s->pipe->conn; + conn->flags = dcerpc_binding_get_flags(binding); + + if (DEBUGLVL(100)) { + conn->flags |= DCERPC_DEBUG_PRINT_BOTH; + } + + if (conn->transport.transport == NCALRPC) { + const char *v = dcerpc_binding_get_string_option(binding, + "auth_type"); + + if (v != NULL && strcmp(v, "ncalrpc_as_system") == 0) { + auth_req = dcerpc_bind_auth_send(c, s->pipe, s->table, + s->credentials, + lpcfg_gensec_settings(c, s->lp_ctx), + DCERPC_AUTH_TYPE_NCALRPC_AS_SYSTEM, + DCERPC_AUTH_LEVEL_CONNECT, + s->table->authservices->names[0]); + composite_continue(c, auth_req, continue_auth, c); + return c; + } + } + + if (cli_credentials_is_anonymous(s->credentials)) { + auth_none_req = dcerpc_bind_auth_none_send(c, s->pipe, s->table); + composite_continue(c, auth_none_req, continue_auth_none, c); + return c; + } + + if ((conn->flags & DCERPC_SCHANNEL) && + !cli_credentials_get_netlogon_creds(s->credentials)) { + /* If we don't already have netlogon credentials for + * the schannel bind, then we have to get these + * first */ + auth_schannel_req = dcerpc_bind_auth_schannel_send(c, s->pipe, s->table, + s->credentials, s->lp_ctx, + dcerpc_auth_level(conn)); + composite_continue(c, auth_schannel_req, continue_auth_schannel, c); + return c; + } + + /* + * we rely on the already authenticated CIFS connection + * if not doing sign or seal + */ + if (conn->transport.transport == NCACN_NP && + !(conn->flags & (DCERPC_PACKET|DCERPC_SIGN|DCERPC_SEAL))) { + auth_none_req = dcerpc_bind_auth_none_send(c, s->pipe, s->table); + composite_continue(c, auth_none_req, continue_auth_none, c); + return c; + } + + + /* Perform an authenticated DCE-RPC bind + */ + if (!(conn->flags & (DCERPC_CONNECT|DCERPC_SEAL|DCERPC_PACKET))) { + /* + we are doing an authenticated connection, + which needs to use [connect], [sign] or [seal]. + If nothing is specified, we default to [sign] now. + This give roughly the same protection as + ncacn_np with smb signing. + */ + conn->flags |= DCERPC_SIGN; + } + + if (conn->flags & DCERPC_AUTH_SPNEGO) { + auth_type = DCERPC_AUTH_TYPE_SPNEGO; + + } else if (conn->flags & DCERPC_AUTH_KRB5) { + auth_type = DCERPC_AUTH_TYPE_KRB5; + + } else if (conn->flags & DCERPC_SCHANNEL) { + auth_type = DCERPC_AUTH_TYPE_SCHANNEL; + + } else if (conn->flags & DCERPC_AUTH_NTLM) { + auth_type = DCERPC_AUTH_TYPE_NTLMSSP; + + } else { + /* try SPNEGO with fallback to NTLMSSP */ + auth_req = dcerpc_bind_auth_send(c, s->pipe, s->table, + s->credentials, + lpcfg_gensec_settings(c, s->lp_ctx), + DCERPC_AUTH_TYPE_SPNEGO, + dcerpc_auth_level(conn), + s->table->authservices->names[0]); + composite_continue(c, auth_req, continue_auth_auto, c); + return c; + } + + auth_req = dcerpc_bind_auth_send(c, s->pipe, s->table, + s->credentials, + lpcfg_gensec_settings(c, s->lp_ctx), + auth_type, + dcerpc_auth_level(conn), + s->table->authservices->names[0]); + composite_continue(c, auth_req, continue_auth, c); + return c; +} + + +/* + Receive result of authenticated bind request on dcerpc pipe + + This returns *p, which may be different to the one originally + supllied, as it rebinds to a new pipe due to authentication fallback + +*/ +NTSTATUS dcerpc_pipe_auth_recv(struct composite_context *c, TALLOC_CTX *mem_ctx, + struct dcerpc_pipe **p) +{ + NTSTATUS status; + + struct pipe_auth_state *s = talloc_get_type(c->private_data, + struct pipe_auth_state); + status = composite_wait(c); + if (!NT_STATUS_IS_OK(status)) { + char *uuid_str = GUID_string(s->pipe, &s->table->syntax_id.uuid); + DEBUG(0, ("Failed to bind to uuid %s for %s %s\n", uuid_str, + dcerpc_binding_string(uuid_str, s->binding), nt_errstr(status))); + talloc_free(uuid_str); + } else { + talloc_steal(mem_ctx, s->pipe); + *p = s->pipe; + } + + talloc_free(c); + return status; +} + + +/* + Perform an authenticated bind if needed - sync version + + This may change *p, as it rebinds to a new pipe due to authentication fallback +*/ +_PUBLIC_ NTSTATUS dcerpc_pipe_auth(TALLOC_CTX *mem_ctx, + struct dcerpc_pipe **p, + const struct dcerpc_binding *binding, + const struct ndr_interface_table *table, + struct cli_credentials *credentials, + struct loadparm_context *lp_ctx) +{ + struct composite_context *c; + + c = dcerpc_pipe_auth_send(*p, binding, table, credentials, lp_ctx); + return dcerpc_pipe_auth_recv(c, mem_ctx, p); +} + + +NTSTATUS dcecli_generic_session_key(struct dcecli_connection *c, + DATA_BLOB *session_key) +{ + if (c != NULL) { + if (c->transport.transport != NCALRPC && + c->transport.transport != NCACN_UNIX_STREAM) + { + return NT_STATUS_LOCAL_USER_SESSION_KEY; + } + } + + return dcerpc_generic_session_key(session_key); +} + +/* + fetch the user session key - may be default (above) or the SMB session key + + The key is always truncated to 16 bytes +*/ +_PUBLIC_ NTSTATUS dcerpc_fetch_session_key(struct dcerpc_pipe *p, + DATA_BLOB *session_key) +{ + NTSTATUS status; + status = p->conn->security_state.session_key(p->conn, session_key); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + session_key->length = MIN(session_key->length, 16); + + return NT_STATUS_OK; +} + +_PUBLIC_ bool dcerpc_transport_encrypted(struct dcerpc_pipe *p) +{ + if (p == NULL) { + return false; + } + + if (p->conn == NULL) { + return false; + } + + return p->conn->transport.encrypted; +} + +/* + create a secondary context from a primary connection + + this uses dcerpc_alter_context() to create a new dcerpc context_id +*/ +_PUBLIC_ NTSTATUS dcerpc_secondary_context(struct dcerpc_pipe *p, + struct dcerpc_pipe **pp2, + const struct ndr_interface_table *table) +{ + NTSTATUS status; + struct dcerpc_pipe *p2; + struct GUID *object = NULL; + + p2 = talloc_zero(p, struct dcerpc_pipe); + if (p2 == NULL) { + return NT_STATUS_NO_MEMORY; + } + p2->conn = talloc_reference(p2, p->conn); + p2->request_timeout = p->request_timeout; + + p2->context_id = ++p->conn->next_context_id; + + p2->syntax = table->syntax_id; + + p2->transfer_syntax = p->transfer_syntax; + + p2->binding = dcerpc_binding_dup(p2, p->binding); + if (p2->binding == NULL) { + talloc_free(p2); + return NT_STATUS_NO_MEMORY; + } + + p2->object = dcerpc_binding_get_object(p2->binding); + if (!GUID_all_zero(&p2->object)) { + object = &p2->object; + } + + p2->binding_handle = dcerpc_pipe_binding_handle(p2, object, table); + if (p2->binding_handle == NULL) { + talloc_free(p2); + return NT_STATUS_NO_MEMORY; + } + + status = dcerpc_alter_context(p2, p2, &p2->syntax, &p2->transfer_syntax); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(p2); + return status; + } + + *pp2 = p2; + + return NT_STATUS_OK; +} diff --git a/source4/librpc/rpc/pyrpc.c b/source4/librpc/rpc/pyrpc.c new file mode 100644 index 0000000..309a6d7 --- /dev/null +++ b/source4/librpc/rpc/pyrpc.c @@ -0,0 +1,653 @@ +/* + Unix SMB/CIFS implementation. + Samba utility functions + Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2008 + + 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 <Python.h> +#include "python/py3compat.h" +#include "includes.h" +#include "python/modules.h" +#include <structmember.h> +#include "librpc/rpc/pyrpc.h" +#include "lib/events/events.h" +#include "param/pyparam.h" +#include "librpc/rpc/dcerpc.h" +#include "librpc/rpc/pyrpc_util.h" +#include "auth/credentials/pycredentials.h" +#include "auth/gensec/gensec.h" + +void initbase(void); + +static PyTypeObject dcerpc_InterfaceType; + +static PyTypeObject *BaseObject_Type; + +static PyTypeObject *ndr_syntax_id_Type; + +static bool PyString_AsGUID(PyObject *object, struct GUID *uuid) +{ + NTSTATUS status; + status = GUID_from_string(PyUnicode_AsUTF8(object), uuid); + if (NT_STATUS_IS_ERR(status)) { + PyErr_SetNTSTATUS(status); + return false; + } + return true; +} + +static bool ndr_syntax_from_py_object(PyObject *object, struct ndr_syntax_id *syntax_id) +{ + ZERO_STRUCTP(syntax_id); + + if (PyUnicode_Check(object)) { + return PyString_AsGUID(object, &syntax_id->uuid); + } else if (PyTuple_Check(object)) { + PyObject *item = NULL; + if (PyTuple_Size(object) < 1 || PyTuple_Size(object) > 2) { + PyErr_SetString(PyExc_ValueError, "Syntax ID tuple has invalid size"); + return false; + } + + item = PyTuple_GetItem(object, 0); + if (!PyUnicode_Check(item)) { + PyErr_SetString(PyExc_ValueError, "Expected GUID as first element in tuple"); + return false; + } + + if (!PyString_AsGUID(item, &syntax_id->uuid)) { + return false; + } + + item = PyTuple_GetItem(object, 1); + if (!PyLong_Check(item)) { + PyErr_SetString(PyExc_ValueError, "Expected version as second element in tuple"); + return false; + } + + syntax_id->if_version = PyLong_AsLong(item); + return true; + } + + PyErr_SetString(PyExc_TypeError, "Expected UUID or syntax id tuple"); + return false; +} + +static PyObject *py_iface_server_name(PyObject *obj, void *closure) +{ + const char *server_name; + dcerpc_InterfaceObject *iface = (dcerpc_InterfaceObject *)obj; + + server_name = dcerpc_server_name(iface->pipe); + if (server_name == NULL) + Py_RETURN_NONE; + + return PyUnicode_FromString(server_name); +} + +static PyObject *py_ndr_syntax_id(struct ndr_syntax_id *syntax_id) +{ + PyObject *ret; + char *uuid_str; + + uuid_str = GUID_string(NULL, &syntax_id->uuid); + if (uuid_str == NULL) + return NULL; + + ret = Py_BuildValue("(s,i)", uuid_str, syntax_id->if_version); + + talloc_free(uuid_str); + + return ret; +} + +static PyObject *py_iface_abstract_syntax(PyObject *obj, void *closure) +{ + dcerpc_InterfaceObject *iface = (dcerpc_InterfaceObject *)obj; + + return py_ndr_syntax_id(&iface->pipe->syntax); +} + +static PyObject *py_iface_transfer_syntax(PyObject *obj, void *closure) +{ + dcerpc_InterfaceObject *iface = (dcerpc_InterfaceObject *)obj; + + return py_ndr_syntax_id(&iface->pipe->transfer_syntax); +} + +static PyObject *py_iface_session_key(PyObject *obj, void *closure) +{ + dcerpc_InterfaceObject *iface = (dcerpc_InterfaceObject *)obj; + DATA_BLOB session_key; + + NTSTATUS status = dcerpc_fetch_session_key(iface->pipe, &session_key); + PyErr_NTSTATUS_IS_ERR_RAISE(status); + + return PyBytes_FromStringAndSize((const char *)session_key.data, session_key.length); +} + +static PyObject *py_iface_user_session_key(PyObject *obj, void *closure) +{ + dcerpc_InterfaceObject *iface = (dcerpc_InterfaceObject *)obj; + TALLOC_CTX *mem_ctx; + NTSTATUS status; + struct gensec_security *security = NULL; + DATA_BLOB session_key = data_blob_null; + static PyObject *session_key_obj = NULL; + + if (iface->pipe == NULL) { + PyErr_SetNTSTATUS(NT_STATUS_NO_USER_SESSION_KEY); + return NULL; + } + + if (iface->pipe->conn == NULL) { + PyErr_SetNTSTATUS(NT_STATUS_NO_USER_SESSION_KEY); + return NULL; + } + + if (iface->pipe->conn->security_state.generic_state == NULL) { + PyErr_SetNTSTATUS(NT_STATUS_NO_USER_SESSION_KEY); + return NULL; + } + + security = iface->pipe->conn->security_state.generic_state; + + mem_ctx = talloc_new(NULL); + + status = gensec_session_key(security, mem_ctx, &session_key); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(mem_ctx); + PyErr_SetNTSTATUS(status); + return NULL; + } + + session_key_obj = PyBytes_FromStringAndSize((const char *)session_key.data, + session_key.length); + talloc_free(mem_ctx); + return session_key_obj; +} + +static PyObject *py_iface_get_timeout(PyObject *obj, void *closure) +{ + dcerpc_InterfaceObject *iface = (dcerpc_InterfaceObject *)obj; + uint32_t timeout; + + timeout = dcerpc_binding_handle_set_timeout(iface->binding_handle, 0); + dcerpc_binding_handle_set_timeout(iface->binding_handle, timeout); + + return PyLong_FromUnsignedLong(timeout); +} + +static int py_iface_set_timeout(PyObject *obj, PyObject *value, void *closure) +{ + dcerpc_InterfaceObject *iface = (dcerpc_InterfaceObject *)obj; + uint32_t timeout; + + timeout = PyLong_AsUnsignedLong(value); + if (PyErr_Occurred() != NULL) { + return -1; + } + + dcerpc_binding_handle_set_timeout(iface->binding_handle, timeout); + return 0; +} + +static PyGetSetDef dcerpc_interface_getsetters[] = { + { + .name = discard_const_p(char, "server_name"), + .get = py_iface_server_name, + .doc = discard_const_p(char, "name of the server, if connected over SMB"), + }, + { + .name = discard_const_p(char, "abstract_syntax"), + .get = py_iface_abstract_syntax, + .doc = discard_const_p(char, "syntax id of the abstract syntax"), + }, + { + .name = discard_const_p(char, "transfer_syntax"), + .get = py_iface_transfer_syntax, + .doc = discard_const_p(char, "syntax id of the transfer syntax"), + }, + { + .name = discard_const_p(char, "session_key"), + .get = py_iface_session_key, + .doc = discard_const_p(char, "session key (as used for blob encryption on LSA and SAMR)"), + }, + { + .name = discard_const_p(char, "user_session_key"), + .get = py_iface_user_session_key, + .doc = discard_const_p(char, "user_session key (as used for blob encryption on DRSUAPI)"), + }, + { + .name = discard_const_p(char, "request_timeout"), + .get = py_iface_get_timeout, + .set = py_iface_set_timeout, + .doc = discard_const_p(char, "request timeout, in seconds"), + }, + { .name = NULL } +}; + +static PyObject *py_iface_request(PyObject *self, PyObject *args, PyObject *kwargs) +{ + dcerpc_InterfaceObject *iface = (dcerpc_InterfaceObject *)self; + int opnum; + DATA_BLOB data_in, data_out; + NTSTATUS status; + char *in_data; + Py_ssize_t in_length; + PyObject *ret; + PyObject *object = NULL; + struct GUID object_guid; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + uint32_t out_flags = 0; + const char *kwnames[] = { "opnum", "data", "object", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "is#|O:request", + discard_const_p(char *, kwnames), &opnum, &in_data, &in_length, &object)) { + talloc_free(mem_ctx); + return NULL; + } + + data_in.data = (uint8_t *)talloc_memdup(mem_ctx, in_data, in_length); + data_in.length = in_length; + + ZERO_STRUCT(data_out); + + if (object != NULL && !PyString_AsGUID(object, &object_guid)) { + talloc_free(mem_ctx); + return NULL; + } + + status = dcerpc_binding_handle_raw_call(iface->binding_handle, + object?&object_guid:NULL, + opnum, + 0, /* in_flags */ + data_in.data, + data_in.length, + mem_ctx, + &data_out.data, + &data_out.length, + &out_flags); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetDCERPCStatus(iface->pipe, status); + talloc_free(mem_ctx); + return NULL; + } + + ret = PyBytes_FromStringAndSize((char *)data_out.data, data_out.length); + + talloc_free(mem_ctx); + return ret; +} + +static PyObject *py_iface_transport_encrypted(PyObject *self) +{ + dcerpc_InterfaceObject *iface = (dcerpc_InterfaceObject *)self; + + if (dcerpc_transport_encrypted(iface->pipe)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyMethodDef dcerpc_interface_methods[] = { + { "request", PY_DISCARD_FUNC_SIG(PyCFunction, py_iface_request), + METH_VARARGS|METH_KEYWORDS, + "S.request(opnum, data, object=None) -> data\n" + "Make a raw request" }, + { "transport_encrypted", PY_DISCARD_FUNC_SIG(PyCFunction, py_iface_transport_encrypted), + METH_NOARGS, + "Check if the DCE transport is encrypted" }, + { NULL, NULL, 0, NULL }, +}; + +static void dcerpc_interface_dealloc(PyObject* self) +{ + dcerpc_InterfaceObject *interface = (dcerpc_InterfaceObject *)self; + + struct tevent_context *ev_save = talloc_reparent( + interface->mem_ctx, NULL, interface->ev); + SMB_ASSERT(ev_save != NULL); + + interface->binding_handle = NULL; + interface->pipe = NULL; + + /* + * Free everything *except* the event context, which must go + * away last + */ + TALLOC_FREE(interface->mem_ctx); + + /* + * Now wish a fond goodbye to the event context itself + */ + talloc_unlink(NULL, ev_save); + self->ob_type->tp_free(self); +} + +static PyObject *dcerpc_interface_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + PyObject *ret; + const char *binding_string = NULL; + PyObject *py_lp_ctx = Py_None; + PyObject *py_credentials = Py_None; + PyObject *syntax = Py_None; + PyObject *py_basis = Py_None; + const char *kwnames[] = { + "binding", "syntax", "lp_ctx", "credentials", "basis_connection", NULL + }; + static struct ndr_interface_table dummy_table; + static struct ndr_interface_string_array dummy_endpoints; + PyObject *args2 = Py_None; + PyObject *kwargs2 = Py_None; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|OOO:connect", discard_const_p(char *, kwnames), &binding_string, &syntax, &py_lp_ctx, &py_credentials, &py_basis)) { + return NULL; + } + + if (strncmp(binding_string, "irpc:", 5) == 0) { + PyErr_SetString(PyExc_ValueError, "irpc: transport not supported"); + return NULL; + } + + /* + * Fill a dummy interface table struct. TODO: In the future, we should + * rather just allow connecting without requiring an interface table. + * + * We just fill the syntax during the connect, but keep the memory valid + * the whole time. + */ + if (!ndr_syntax_from_py_object(syntax, &dummy_table.syntax_id)) { + return NULL; + } + + /* + * Initialise the endpoints list in dummy_table if required + */ + if (dummy_table.endpoints == NULL) { + dummy_table.endpoints = &dummy_endpoints; + } + + args2 = Py_BuildValue("(s)", binding_string); + if (args2 == NULL) { + return NULL; + } + + kwargs2 = Py_BuildValue("{s:O,s:O,s:O}", + "lp_ctx", py_lp_ctx, + "credentials", py_credentials, + "basis_connection", py_basis); + if (kwargs2 == NULL) { + Py_DECREF(args2); + return NULL; + } + + ret = py_dcerpc_interface_init_helper(type, args2, kwargs2, &dummy_table); + ZERO_STRUCT(dummy_table.syntax_id); + Py_DECREF(args2); + Py_DECREF(kwargs2); + return ret; +} + +static PyTypeObject dcerpc_InterfaceType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "dcerpc.ClientConnection", + .tp_basicsize = sizeof(dcerpc_InterfaceObject), + .tp_dealloc = dcerpc_interface_dealloc, + .tp_getset = dcerpc_interface_getsetters, + .tp_methods = dcerpc_interface_methods, + .tp_doc = "ClientConnection(binding, syntax, lp_ctx=None, credentials=None) -> connection\n" +"\n" +"binding should be a DCE/RPC binding string (for example: ncacn_ip_tcp:127.0.0.1)\n" +"syntax should be a tuple with a GUID and version number of an interface\n" +"lp_ctx should be a path to a smb.conf file or a param.LoadParm object\n" +"credentials should be a credentials.Credentials object.\n\n", + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_new = dcerpc_interface_new, +}; + +static PyObject *py_transfer_syntax_ndr_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + return py_dcerpc_syntax_init_helper(type, args, kwargs, &ndr_transfer_syntax_ndr); +} + +static PyTypeObject py_transfer_syntax_ndr_SyntaxType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "base.transfer_syntax_ndr", + .tp_doc = "transfer_syntax_ndr()\n", + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_new = py_transfer_syntax_ndr_new, +}; + +static PyObject *py_transfer_syntax_ndr64_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + return py_dcerpc_syntax_init_helper(type, args, kwargs, &ndr_transfer_syntax_ndr64); +} + +static PyTypeObject py_transfer_syntax_ndr64_SyntaxType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "base.transfer_syntax_ndr64", + .tp_doc = "transfer_syntax_ndr64()\n", + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_new = py_transfer_syntax_ndr64_new, +}; + +static PyObject *py_bind_time_features_syntax_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + const char *kwnames[] = { + "features", NULL + }; + unsigned long long features = 0; + struct ndr_syntax_id syntax; + PyObject *args2 = Py_None; + PyObject *kwargs2 = Py_None; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "K:features", discard_const_p(char *, kwnames), &features)) { + return NULL; + } + + args2 = Py_BuildValue("()"); + if (args2 == NULL) { + return NULL; + } + + kwargs2 = Py_BuildValue("{}"); + if (kwargs2 == NULL) { + Py_DECREF(args2); + return NULL; + } + + syntax = dcerpc_construct_bind_time_features(features); + + return py_dcerpc_syntax_init_helper(type, args2, kwargs2, &syntax); +} + +static PyTypeObject py_bind_time_features_syntax_SyntaxType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "base.bind_time_features_syntax", + .tp_doc = "bind_time_features_syntax(features)\n", + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_new = py_bind_time_features_syntax_new, +}; + +struct py_dcerpc_ndr_pointer { + PyObject *value; +}; + +static void py_dcerpc_ndr_pointer_dealloc(PyObject* self) +{ + struct py_dcerpc_ndr_pointer *obj = + pytalloc_get_type(self, struct py_dcerpc_ndr_pointer); + + Py_DECREF(obj->value); + obj->value = NULL; + + self->ob_type->tp_free(self); +} + +static PyObject *py_dcerpc_ndr_pointer_get_value(PyObject *self, void *closure) +{ + struct py_dcerpc_ndr_pointer *obj = + pytalloc_get_type(self, struct py_dcerpc_ndr_pointer); + + Py_INCREF(obj->value); + return obj->value; +} + +static int py_dcerpc_ndr_pointer_set_value(PyObject *self, PyObject *value, void *closure) +{ + struct py_dcerpc_ndr_pointer *obj = + pytalloc_get_type(self, struct py_dcerpc_ndr_pointer); + + Py_DECREF(obj->value); + obj->value = value; + Py_INCREF(obj->value); + return 0; +} + +static PyGetSetDef py_dcerpc_ndr_pointer_getsetters[] = { + { + .name = discard_const_p(char, "value"), + .get = py_dcerpc_ndr_pointer_get_value, + .set = py_dcerpc_ndr_pointer_set_value, + .doc = discard_const_p(char, "the value store by the pointer"), + }, + { + .name = NULL, + }, +}; + +static PyObject *py_dcerpc_ndr_pointer_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + PyObject *ret = NULL; + struct py_dcerpc_ndr_pointer *obj = NULL; + const char *kwnames[] = { "value", NULL }; + PyObject *value = NULL; + bool ok; + + ok = PyArg_ParseTupleAndKeywords(args, kwargs, "O:value", + discard_const_p(char *, kwnames), + &value); + if (!ok) { + return NULL; + } + + ret = pytalloc_new(struct py_dcerpc_ndr_pointer, type); + if (ret == NULL) { + return NULL; + } + + obj = pytalloc_get_type(ret, struct py_dcerpc_ndr_pointer); + *obj = (struct py_dcerpc_ndr_pointer) { + .value = value, + }; + + Py_INCREF(obj->value); + return ret; +} + +static PyTypeObject py_dcerpc_ndr_pointer_type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "base.ndr_pointer", + .tp_dealloc = py_dcerpc_ndr_pointer_dealloc, + .tp_getset = py_dcerpc_ndr_pointer_getsetters, + .tp_doc = "ndr_pointer(value)\n", + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_new = py_dcerpc_ndr_pointer_new, +}; + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + .m_name = "base", + .m_doc = "DCE/RPC protocol implementation", + .m_size = -1, +}; + +MODULE_INIT_FUNC(base) +{ + PyObject *m; + PyObject *dep_talloc; + PyObject *dep_samba_dcerpc_misc; + + dep_talloc = PyImport_ImportModule("talloc"); + if (dep_talloc == NULL) + return NULL; + + BaseObject_Type = (PyTypeObject *)PyObject_GetAttrString(dep_talloc, "BaseObject"); + if (BaseObject_Type == NULL) { + Py_CLEAR(dep_talloc); + return NULL; + } + + Py_CLEAR(dep_talloc); + dep_samba_dcerpc_misc = PyImport_ImportModule("samba.dcerpc.misc"); + if (dep_samba_dcerpc_misc == NULL) { + return NULL; + } + + ndr_syntax_id_Type = (PyTypeObject *)PyObject_GetAttrString(dep_samba_dcerpc_misc, "ndr_syntax_id"); + Py_CLEAR(dep_samba_dcerpc_misc); + if (ndr_syntax_id_Type == NULL) { + return NULL; + } + + py_transfer_syntax_ndr_SyntaxType.tp_base = ndr_syntax_id_Type; + py_transfer_syntax_ndr_SyntaxType.tp_basicsize = pytalloc_BaseObject_size(); + py_transfer_syntax_ndr64_SyntaxType.tp_base = ndr_syntax_id_Type; + py_transfer_syntax_ndr64_SyntaxType.tp_basicsize = pytalloc_BaseObject_size(); + py_bind_time_features_syntax_SyntaxType.tp_base = ndr_syntax_id_Type; + py_bind_time_features_syntax_SyntaxType.tp_basicsize = pytalloc_BaseObject_size(); + + py_dcerpc_ndr_pointer_type.tp_base = BaseObject_Type; + py_dcerpc_ndr_pointer_type.tp_basicsize = pytalloc_BaseObject_size(); + + if (PyType_Ready(&dcerpc_InterfaceType) < 0) { + return NULL; + } + + if (PyType_Ready(&py_transfer_syntax_ndr_SyntaxType) < 0) { + return NULL; + } + if (PyType_Ready(&py_transfer_syntax_ndr64_SyntaxType) < 0) { + return NULL; + } + if (PyType_Ready(&py_bind_time_features_syntax_SyntaxType) < 0) { + return NULL; + } + + if (PyType_Ready(&py_dcerpc_ndr_pointer_type) < 0) { + return NULL; + } + + m = PyModule_Create(&moduledef); + if (m == NULL) { + return NULL; + } + + Py_INCREF((PyObject *)&dcerpc_InterfaceType); + PyModule_AddObject(m, "ClientConnection", (PyObject *)&dcerpc_InterfaceType); + + Py_INCREF((PyObject *)(void *)&py_transfer_syntax_ndr_SyntaxType); + PyModule_AddObject(m, "transfer_syntax_ndr", (PyObject *)(void *)&py_transfer_syntax_ndr_SyntaxType); + Py_INCREF((PyObject *)(void *)&py_transfer_syntax_ndr64_SyntaxType); + PyModule_AddObject(m, "transfer_syntax_ndr64", (PyObject *)(void *)&py_transfer_syntax_ndr64_SyntaxType); + Py_INCREF((PyObject *)(void *)&py_bind_time_features_syntax_SyntaxType); + PyModule_AddObject(m, "bind_time_features_syntax", (PyObject *)(void *)&py_bind_time_features_syntax_SyntaxType); + Py_INCREF((PyObject *)(void *)&py_dcerpc_ndr_pointer_type); + PyModule_AddObject(m, "ndr_pointer", (PyObject *)(void *)&py_dcerpc_ndr_pointer_type); + return m; +} diff --git a/source4/librpc/rpc/pyrpc.h b/source4/librpc/rpc/pyrpc.h new file mode 100644 index 0000000..311ba2d --- /dev/null +++ b/source4/librpc/rpc/pyrpc.h @@ -0,0 +1,60 @@ +/* + Unix SMB/CIFS implementation. + Samba utility functions + Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2008 + + 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/>. +*/ + +#ifndef _PYRPC_H_ +#define _PYRPC_H_ + +#include "libcli/util/pyerrors.h" + +#define PY_CHECK_TYPE(type, var, fail) \ + if (var == NULL) { \ + PyErr_Format(PyExc_TypeError, \ + __location__ \ + ": Expected type '%s' for '%s', got NULL", \ + (type)->tp_name, #var); \ + fail; \ + } else if (!PyObject_TypeCheck(var, type)) { \ + PyErr_Format(PyExc_TypeError, \ + __location__ \ + ": Expected type '%s' for '%s' of type '%s'", \ + (type)->tp_name, #var, Py_TYPE(var)->tp_name); \ + fail; \ + } + +#define dom_sid0_Type dom_sid_Type +#define dom_sid2_Type dom_sid_Type +#define dom_sid28_Type dom_sid_Type +#define dom_sid0_Check dom_sid_Check +#define dom_sid2_Check dom_sid_Check +#define dom_sid28_Check dom_sid_Check + +typedef struct { + PyObject_HEAD + TALLOC_CTX *mem_ctx; + struct dcerpc_pipe *pipe; + struct dcerpc_binding_handle *binding_handle; + struct tevent_context *ev; +} dcerpc_InterfaceObject; + + +#ifndef NDR_DCERPC_REQUEST_OBJECT_PRESENT +#define NDR_DCERPC_REQUEST_OBJECT_PRESENT LIBNDR_FLAG_OBJECT_PRESENT +#endif /* NDR_DCERPC_REQUEST_OBJECT_PRESENT */ + +#endif /* _PYRPC_H_ */ diff --git a/source4/librpc/rpc/pyrpc_util.c b/source4/librpc/rpc/pyrpc_util.c new file mode 100644 index 0000000..0d6a165 --- /dev/null +++ b/source4/librpc/rpc/pyrpc_util.c @@ -0,0 +1,506 @@ +/* + Unix SMB/CIFS implementation. + + Python interface to DCE/RPC library - utility functions. + + Copyright (C) 2010 Jelmer Vernooij <jelmer@samba.org> + Copyright (C) 2010 Andrew Tridgell <tridge@samba.org> + + 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 <Python.h> +#include "python/py3compat.h" +#include "includes.h" +#include "python/modules.h" +#include "librpc/rpc/pyrpc_util.h" +#include "librpc/rpc/dcerpc.h" +#include "librpc/rpc/pyrpc.h" +#include "param/pyparam.h" +#include "auth/credentials/pycredentials.h" +#include "lib/events/events.h" +#include "lib/messaging/messaging.h" +#include "lib/messaging/irpc.h" + +bool py_check_dcerpc_type(PyObject *obj, const char *module, const char *type_name) +{ + PyObject *mod; + PyTypeObject *type; + bool ret; + + mod = PyImport_ImportModule(module); + + if (mod == NULL) { + PyErr_Format(PyExc_RuntimeError, "Unable to import %s to check type %s", + module, type_name); + return false; + } + + type = (PyTypeObject *)PyObject_GetAttrString(mod, type_name); + Py_DECREF(mod); + if (type == NULL) { + PyErr_Format(PyExc_RuntimeError, "Unable to find type %s in module %s", + module, type_name); + return false; + } + + ret = PyObject_TypeCheck(obj, type); + Py_DECREF(type); + + if (!ret) + PyErr_Format(PyExc_TypeError, "Expected type %s.%s, got %s", + module, type_name, Py_TYPE(obj)->tp_name); + + return ret; +} + +/* + connect to a IRPC pipe from python + */ +static NTSTATUS pyrpc_irpc_connect(TALLOC_CTX *mem_ctx, const char *irpc_server, + const struct ndr_interface_table *table, + struct tevent_context *event_ctx, + struct loadparm_context *lp_ctx, + struct dcerpc_binding_handle **binding_handle) +{ + struct imessaging_context *msg; + + msg = imessaging_client_init(mem_ctx, lp_ctx, event_ctx); + NT_STATUS_HAVE_NO_MEMORY(msg); + + *binding_handle = irpc_binding_handle_by_name(mem_ctx, msg, irpc_server, table); + if (*binding_handle == NULL) { + talloc_free(msg); + return NT_STATUS_INVALID_PIPE_STATE; + } + + /* + * Note: this allows nested event loops to happen, + * but as there's no top level event loop it's not that critical. + */ + dcerpc_binding_handle_set_sync_ev(*binding_handle, event_ctx); + + return NT_STATUS_OK; +} + +PyObject *py_dcerpc_interface_init_helper(PyTypeObject *type, PyObject *args, PyObject *kwargs, + const struct ndr_interface_table *table) +{ + dcerpc_InterfaceObject *ret; + const char *binding_string; + PyObject *py_lp_ctx = Py_None, *py_credentials = Py_None, *py_basis = Py_None; + NTSTATUS status; + unsigned int timeout = (unsigned int)-1; + const char *kwnames[] = { + "binding", "lp_ctx", "credentials", "timeout", "basis_connection", NULL + }; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|OOIO:samr", discard_const_p(char *, kwnames), &binding_string, &py_lp_ctx, &py_credentials, &timeout, &py_basis)) { + return NULL; + } + + status = dcerpc_init(); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + return NULL; + } + + ret = PyObject_New(dcerpc_InterfaceObject, type); + if (ret == NULL) { + PyErr_NoMemory(); + return NULL; + } + + ret->pipe = NULL; + ret->binding_handle = NULL; + ret->ev = NULL; + ret->mem_ctx = talloc_new(NULL); + if (ret->mem_ctx == NULL) { + PyErr_NoMemory(); + return NULL; + } + + if (strncmp(binding_string, "irpc:", 5) == 0) { + struct loadparm_context *lp_ctx; + + ret->ev = s4_event_context_init(ret->mem_ctx); + if (ret->ev == NULL) { + PyErr_SetString(PyExc_TypeError, + "Unable to initialise event context"); + Py_DECREF(ret); + return NULL; + } + + lp_ctx = lpcfg_from_py_object(ret->ev, py_lp_ctx); + if (lp_ctx == NULL) { + PyErr_SetString(PyExc_TypeError, "Expected loadparm context"); + Py_DECREF(ret); + return NULL; + } + + status = pyrpc_irpc_connect(ret->mem_ctx, binding_string+5, table, + ret->ev, lp_ctx, &ret->binding_handle); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + Py_DECREF(ret); + return NULL; + } + } else if (py_basis != Py_None) { + struct dcerpc_pipe *base_pipe; + PyObject *py_base; + PyTypeObject *ClientConnection_Type; + + py_base = PyImport_ImportModule("samba.dcerpc.base"); + if (py_base == NULL) { + Py_DECREF(ret); + return NULL; + } + + ClientConnection_Type = (PyTypeObject *)PyObject_GetAttrString(py_base, "ClientConnection"); + if (ClientConnection_Type == NULL) { + PyErr_SetNone(PyExc_TypeError); + Py_DECREF(ret); + Py_DECREF(py_base); + return NULL; + } + + if (!PyObject_TypeCheck(py_basis, ClientConnection_Type)) { + PyErr_SetString(PyExc_TypeError, "basis_connection must be a DCE/RPC connection"); + Py_DECREF(ret); + Py_DECREF(py_base); + Py_DECREF(ClientConnection_Type); + return NULL; + } + + base_pipe = talloc_reference(ret->mem_ctx, + ((dcerpc_InterfaceObject *)py_basis)->pipe); + if (base_pipe == NULL) { + PyErr_NoMemory(); + Py_DECREF(ret); + Py_DECREF(py_base); + Py_DECREF(ClientConnection_Type); + return NULL; + } + + ret->ev = talloc_reference( + ret->mem_ctx, + ((dcerpc_InterfaceObject *)py_basis)->ev); + if (ret->ev == NULL) { + PyErr_NoMemory(); + Py_DECREF(ret); + Py_DECREF(py_base); + Py_DECREF(ClientConnection_Type); + return NULL; + } + + status = dcerpc_secondary_context(base_pipe, &ret->pipe, table); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + Py_DECREF(ret); + Py_DECREF(py_base); + Py_DECREF(ClientConnection_Type); + return NULL; + } + + ret->pipe = talloc_steal(ret->mem_ctx, ret->pipe); + Py_XDECREF(ClientConnection_Type); + Py_XDECREF(py_base); + } else { + struct loadparm_context *lp_ctx; + struct cli_credentials *credentials; + + ret->ev = s4_event_context_init(ret->mem_ctx); + if (ret->ev == NULL) { + PyErr_SetString(PyExc_TypeError, "Expected loadparm context"); + Py_DECREF(ret); + return NULL; + } + + lp_ctx = lpcfg_from_py_object(ret->ev, py_lp_ctx); + if (lp_ctx == NULL) { + PyErr_SetString(PyExc_TypeError, "Expected loadparm context"); + Py_DECREF(ret); + return NULL; + } + + credentials = cli_credentials_from_py_object(py_credentials); + if (credentials == NULL) { + PyErr_SetString(PyExc_TypeError, "Expected credentials"); + Py_DECREF(ret); + return NULL; + } + status = dcerpc_pipe_connect(ret->mem_ctx, &ret->pipe, binding_string, + table, credentials, ret->ev, lp_ctx); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + Py_DECREF(ret); + return NULL; + } + } + + if (ret->pipe) { + ret->pipe->conn->flags |= DCERPC_NDR_REF_ALLOC; + ret->binding_handle = ret->pipe->binding_handle; + } + + /* reset timeout for the handle */ + if ((timeout != ((unsigned int)-1)) && (ret->binding_handle != NULL)) { + dcerpc_binding_handle_set_timeout(ret->binding_handle, timeout); + } + + return (PyObject *)ret; +} + +static PyObject *py_dcerpc_run_function(dcerpc_InterfaceObject *iface, + const struct PyNdrRpcMethodDef *md, + PyObject *args, PyObject *kwargs) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS status; + void *r; + PyObject *result = Py_None; + + if (md->pack_in_data == NULL || md->unpack_out_data == NULL) { + PyErr_SetString(PyExc_NotImplementedError, "No marshalling code available yet"); + return NULL; + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + PyErr_NoMemory(); + return NULL; + } + + r = talloc_zero_size(mem_ctx, md->table->calls[md->opnum].struct_size); + if (r == NULL) { + PyErr_NoMemory(); + return NULL; + } + + if (!md->pack_in_data(args, kwargs, r)) { + talloc_free(mem_ctx); + return NULL; + } + + status = md->call(iface->binding_handle, mem_ctx, r); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetDCERPCStatus(iface->pipe, status); + talloc_free(mem_ctx); + return NULL; + } + + result = md->unpack_out_data(r); + + talloc_free(mem_ctx); + return result; +} + +static PyObject *py_dcerpc_call_wrapper(PyObject *self, PyObject *args, void *wrapped, PyObject *kwargs) +{ + dcerpc_InterfaceObject *iface = (dcerpc_InterfaceObject *)self; + const struct PyNdrRpcMethodDef *md = (const struct PyNdrRpcMethodDef *)wrapped; + + return py_dcerpc_run_function(iface, md, args, kwargs); +} + +bool PyInterface_AddNdrRpcMethods(PyTypeObject *ifacetype, const struct PyNdrRpcMethodDef *mds) +{ + int i; + for (i = 0; mds[i].name; i++) { + PyObject *ret; + struct wrapperbase *wb = (struct wrapperbase *)calloc(sizeof(struct wrapperbase), 1); + + if (wb == NULL) { + return false; + } + wb->name = discard_const_p(char, mds[i].name); + wb->flags = PyWrapperFlag_KEYWORDS; + wb->wrapper = PY_DISCARD_FUNC_SIG(wrapperfunc, + py_dcerpc_call_wrapper); + wb->doc = discard_const_p(char, mds[i].doc); + + ret = PyDescr_NewWrapper(ifacetype, wb, discard_const_p(void, &mds[i])); + + PyDict_SetItemString(ifacetype->tp_dict, mds[i].name, + (PyObject *)ret); + Py_CLEAR(ret); + } + + return true; +} + +PyObject *py_dcerpc_syntax_init_helper(PyTypeObject *type, PyObject *args, PyObject *kwargs, + const struct ndr_syntax_id *syntax) +{ + PyObject *ret; + struct ndr_syntax_id *obj; + const char *kwnames[] = { NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, ":abstract_syntax", discard_const_p(char *, kwnames))) { + return NULL; + } + + ret = pytalloc_new(struct ndr_syntax_id, type); + if (ret == NULL) { + return NULL; + } + + obj = pytalloc_get_type(ret, struct ndr_syntax_id); + *obj = *syntax; + + return ret; +} + +void PyErr_SetDCERPCStatus(struct dcerpc_pipe *p, NTSTATUS status) +{ + if (p && NT_STATUS_EQUAL(status, NT_STATUS_NET_WRITE_FAULT)) { + status = dcerpc_fault_to_nt_status(p->last_fault_code); + } + PyErr_SetNTSTATUS(status); +} + + +/* + take a NDR structure that has a type in a python module and return + it as a python object + + r is the NDR structure pointer (a C structure) + + r_ctx is the context that is a parent of r. It will be referenced by + the resulting python object + + This MUST only be used by objects that are based on pytalloc_Object + otherwise the pytalloc_reference_ex() will fail. + */ +PyObject *py_return_ndr_struct(const char *module_name, const char *type_name, + TALLOC_CTX *r_ctx, void *r) +{ + PyTypeObject *py_type; + PyObject *module; + PyObject *result = NULL; + + if (r == NULL) { + Py_RETURN_NONE; + } + + module = PyImport_ImportModule(module_name); + if (module == NULL) { + return NULL; + } + + py_type = (PyTypeObject *)PyObject_GetAttrString(module, type_name); + if (py_type == NULL) { + Py_DECREF(module); + return NULL; + } + + result = pytalloc_reference_ex(py_type, r_ctx, r); + Py_CLEAR(module); + Py_CLEAR(py_type); + return result; +} + +PyObject *PyString_FromStringOrNULL(const char *str) +{ + if (str == NULL) { + Py_RETURN_NONE; + } + return PyUnicode_FromString(str); +} + +PyObject *pyrpc_import_union(PyTypeObject *type, TALLOC_CTX *mem_ctx, int level, + const void *in, const char *typename) +{ + PyObject *mem_ctx_obj = NULL; + PyObject *in_obj = NULL; + PyObject *ret = NULL; + + mem_ctx_obj = pytalloc_GenericObject_reference(mem_ctx); + if (mem_ctx_obj == NULL) { + return NULL; + } + + in_obj = pytalloc_GenericObject_reference_ex(mem_ctx, discard_const(in)); + if (in_obj == NULL) { + Py_XDECREF(mem_ctx_obj); + return NULL; + } + + ret = PyObject_CallMethod((PyObject *)type, + discard_const_p(char, "__import__"), + discard_const_p(char, "OiO"), + mem_ctx_obj, level, in_obj); + Py_XDECREF(mem_ctx_obj); + Py_XDECREF(in_obj); + if (ret == NULL) { + return NULL; + } + + return ret; +} + +void *pyrpc_export_union(PyTypeObject *type, TALLOC_CTX *mem_ctx, int level, + PyObject *in, const char *typename) +{ + PyObject *mem_ctx_obj = NULL; + PyObject *ret_obj = NULL; + void *ret = NULL; + + mem_ctx_obj = pytalloc_GenericObject_reference(mem_ctx); + if (mem_ctx_obj == NULL) { + return NULL; + } + + ret_obj = PyObject_CallMethod((PyObject *)type, + discard_const_p(char, "__export__"), + discard_const_p(char, "OiO"), + mem_ctx_obj, level, in); + Py_XDECREF(mem_ctx_obj); + if (ret_obj == NULL) { + return NULL; + } + + ret = _pytalloc_get_type(ret_obj, typename); + Py_XDECREF(ret_obj); + return ret; +} + +PyObject *py_dcerpc_ndr_pointer_deref(PyTypeObject *type, PyObject *obj) +{ + if (!PyObject_TypeCheck(obj, type)) { + PyErr_Format(PyExc_TypeError, + "Expected type '%s' but got type '%s'", + (type)->tp_name, Py_TYPE(obj)->tp_name); + return NULL; + } + + return PyObject_GetAttrString(obj, discard_const_p(char, "value")); +} + +PyObject *py_dcerpc_ndr_pointer_wrap(PyTypeObject *type, PyObject *obj) +{ + PyObject *args = NULL; + PyObject *ret_obj = NULL; + + args = PyTuple_New(1); + if (args == NULL) { + return NULL; + } + Py_XINCREF(obj); + PyTuple_SetItem(args, 0, obj); + + ret_obj = PyObject_Call((PyObject *)type, args, NULL); + Py_XDECREF(args); + return ret_obj; +} diff --git a/source4/librpc/rpc/pyrpc_util.h b/source4/librpc/rpc/pyrpc_util.h new file mode 100644 index 0000000..5a5f14d --- /dev/null +++ b/source4/librpc/rpc/pyrpc_util.h @@ -0,0 +1,70 @@ +/* + Unix SMB/CIFS implementation. + + Python interface to DCE/RPC library - utility functions. + + Copyright (C) 2010 Jelmer Vernooij <jelmer@samba.org> + Copyright (C) 2010 Andrew Tridgell <tridge@samba.org> + + 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/>. +*/ + +#ifndef __PYRPC_UTIL_H__ +#define __PYRPC_UTIL_H__ + +#include "librpc/rpc/pyrpc.h" + +#define PyErr_FromNdrError(err) Py_BuildValue("(is)", err, ndr_map_error2string(err)) + +#define PyErr_SetNdrError(err) \ + PyErr_SetObject(PyExc_RuntimeError, PyErr_FromNdrError(err)) + +void PyErr_SetDCERPCStatus(struct dcerpc_pipe *p, NTSTATUS status); + +typedef NTSTATUS (*py_dcerpc_call_fn) (struct dcerpc_binding_handle *, TALLOC_CTX *, void *); +typedef bool (*py_data_pack_fn) (PyObject *args, PyObject *kwargs, void *r); +typedef PyObject *(*py_data_unpack_fn) (void *r); + +struct PyNdrRpcMethodDef { + const char *name; + const char *doc; + py_dcerpc_call_fn call; + py_data_pack_fn pack_in_data; + py_data_unpack_fn unpack_out_data; + uint32_t opnum; + const struct ndr_interface_table *table; +}; + +bool py_check_dcerpc_type(PyObject *obj, const char *module, const char *type_name); +bool PyInterface_AddNdrRpcMethods(PyTypeObject *object, const struct PyNdrRpcMethodDef *mds); +PyObject *py_dcerpc_interface_init_helper(PyTypeObject *type, PyObject *args, PyObject *kwargs, const struct ndr_interface_table *table); + +struct ndr_syntax_id; +PyObject *py_dcerpc_syntax_init_helper(PyTypeObject *type, PyObject *args, PyObject *kwargs, + const struct ndr_syntax_id *syntax); + +PyObject *py_return_ndr_struct(const char *module_name, const char *type_name, + TALLOC_CTX *r_ctx, void *r); + +PyObject *PyString_FromStringOrNULL(const char *str); + +PyObject *pyrpc_import_union(PyTypeObject *type, TALLOC_CTX *mem_ctx, int level, + const void *in, const char *typename); +void *pyrpc_export_union(PyTypeObject *type, TALLOC_CTX *mem_ctx, int level, + PyObject *in, const char *typename); + +PyObject *py_dcerpc_ndr_pointer_deref(PyTypeObject *type, PyObject *obj); +PyObject *py_dcerpc_ndr_pointer_wrap(PyTypeObject *type, PyObject *obj); + +#endif /* __PYRPC_UTIL_H__ */ |