diff options
author | Samuel Cabrero <samuelcabrero@kernevil.me> | 2014-09-16 16:41:27 +0200 |
---|---|---|
committer | Stefan Metzmacher <metze@samba.org> | 2014-09-22 23:09:08 +0200 |
commit | d617230888840f0d89b1d30c851f5484c1f8182d (patch) | |
tree | 28816432ede1d6e2010e729e870b3f31366d17b7 /source4/librpc | |
parent | cc55bc2d45df3406130a5fe127f5eb35e466a7cd (diff) | |
download | samba-d617230888840f0d89b1d30c851f5484c1f8182d.tar.gz samba-d617230888840f0d89b1d30c851f5484c1f8182d.tar.xz samba-d617230888840f0d89b1d30c851f5484c1f8182d.zip |
ncacn_http: Client implementation
Signed-off-by: Samuel Cabrero <samuelcabrero@kernevil.me>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Diffstat (limited to 'source4/librpc')
-rw-r--r-- | source4/librpc/rpc/dcerpc.h | 2 | ||||
-rw-r--r-- | source4/librpc/rpc/dcerpc_roh.c | 789 | ||||
-rw-r--r-- | source4/librpc/rpc/dcerpc_roh.h | 116 | ||||
-rw-r--r-- | source4/librpc/rpc/dcerpc_roh_channel_in.c | 471 | ||||
-rw-r--r-- | source4/librpc/rpc/dcerpc_roh_channel_out.c | 743 | ||||
-rwxr-xr-x | source4/librpc/wscript_build | 3 |
6 files changed, 2123 insertions, 1 deletions
diff --git a/source4/librpc/rpc/dcerpc.h b/source4/librpc/rpc/dcerpc.h index 18be157967..a7d1694770 100644 --- a/source4/librpc/rpc/dcerpc.h +++ b/source4/librpc/rpc/dcerpc.h @@ -139,6 +139,8 @@ 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, diff --git a/source4/librpc/rpc/dcerpc_roh.c b/source4/librpc/rpc/dcerpc_roh.c new file mode 100644 index 0000000000..09072940f9 --- /dev/null +++ b/source4/librpc/rpc/dcerpc_roh.c @@ -0,0 +1,789 @@ +/* + 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" + +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; + bool use_ntlm; +}; + +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 = roh_stream_ctx->roh_conn->default_channel_in->send_queue; + + 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, + bool use_ntlm) +{ + 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->use_ntlm = use_ntlm; + + 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) { + status = tstream_tls_params_client(state->roh, NULL, NULL, + &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_in_send(state, + state->event_ctx, + state->rpcproxy_addresses[state->rpcproxy_address_index], + state->rpc_proxy_port, + state->credentials, + state->roh, 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_in_recv(subreq); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + subreq = roh_connect_channel_out_send(state, + state->event_ctx, + state->rpcproxy_addresses[state->rpcproxy_address_index], + state->rpc_proxy_port, + state->credentials, + state->roh, + 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_out_recv(subreq); + 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->use_ntlm); + 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->use_ntlm); + 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; + + ctx = tstream_context_data(stream, struct tstream_roh_context); + if (!ctx->roh_conn) { + errno = ENOTCONN; + return -1; + } + + return tstream_pending_bytes(ctx->roh_conn->default_channel_out->streams.active); +} + +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; + + 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; + } + if (!ctx->roh_conn->default_channel_out->streams.active) { + tevent_req_error(req, ENOTCONN); + goto post; + } + + state->roh_conn = ctx->roh_conn; + + subreq = tstream_readv_send(state, ev, + ctx->roh_conn->default_channel_out->streams.active, + 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; + + 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; + } + if (!ctx->roh_conn->default_channel_in->streams.active) { + tevent_req_error(req, ENOTCONN); + goto post; + } + + state->roh_conn = ctx->roh_conn; + + subreq = tstream_writev_send(state, ev, + ctx->roh_conn->default_channel_in->streams.active, + 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; + } + if (!ctx->roh_conn->default_channel_in->streams.active) { + tevent_req_error(req, ENOTCONN); + goto post; + } + + subreq = tstream_disconnect_send(state, ev, ctx->roh_conn->default_channel_in->streams.active); + 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; + int sys_errno; + + 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 = tstream_disconnect_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (ret == -1) { + tevent_req_error(req, sys_errno); + return; + } + TALLOC_FREE(roh_stream->roh_conn->default_channel_in); + + subreq = tstream_disconnect_send(state, + state->ev, + roh_stream->roh_conn->default_channel_out->streams.raw); + 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; + int sys_errno; + + 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 = tstream_disconnect_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (ret == -1) { + tevent_req_error(req, sys_errno); + 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 0000000000..c4c577ed72 --- /dev/null +++ b/source4/librpc/rpc/dcerpc_roh.h @@ -0,0 +1,116 @@ +/* + 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 tevent_queue *send_queue; + struct { + struct tstream_context *raw; + struct tstream_context *tls; + struct tstream_context *active; + } streams; +}; + +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 0000000000..887a57b67f --- /dev/null +++ b/source4/librpc/rpc/dcerpc_roh_channel_in.c @@ -0,0 +1,471 @@ +/* + 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 "lib/tevent/tevent.h" +#include "lib/talloc/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 "lib/http/http.h" + +struct roh_connect_channel_state { + struct tevent_context *ev; + struct tsocket_address *local_address; + struct tsocket_address *remote_address; + struct cli_credentials *credentials; + struct roh_connection *roh; + bool tls; + struct tstream_tls_params *tls_params; +}; + +static void roh_connect_channel_in_done(struct tevent_req *); +struct tevent_req *roh_connect_channel_in_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *rpcproxy_ip_address, + unsigned int rpcproxy_port, + struct cli_credentials *credentials, + struct roh_connection *roh, + bool tls, + struct tstream_tls_params *tls_params) +{ + NTSTATUS status; + struct tevent_req *req; + struct tevent_req *subreq; + struct roh_connect_channel_state *state; + int ret; + + DEBUG(8, ("%s: Connecting channel in socket, RPC proxy is %s:%d (TLS: %s)\n", + __func__, 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)) { + DEBUG(0, ("%s: Invalid host (%s), needs to be an IP address\n", + __func__, rpcproxy_ip_address)); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + state->ev = ev; + state->credentials = credentials; + state->roh = roh; + state->tls = tls; + state->tls_params = tls_params; + ret = tsocket_address_inet_from_strings(state, "ip", NULL, 0, + &state->local_address); + if (ret != 0) { + DEBUG(0, ("%s: Cannot create local socket address, error: %s (%d)\n", + __func__, strerror(errno), errno)); + status = map_nt_error_from_unix_common(errno); + tevent_req_nterror(req, status); + return tevent_req_post(req, ev); + } + + ret = tsocket_address_inet_from_strings(state, "ip", + rpcproxy_ip_address, + rpcproxy_port, + &state->remote_address); + if (ret != 0) { + DEBUG(0, ("%s: Cannot create remote socket address, error: %s (%d)\n", + __func__, strerror(errno), errno)); + status = map_nt_error_from_unix_common(errno); + tevent_req_nterror(req, status); + return tevent_req_post(req, ev); + } + + /* Initialize channel structure */ + state->roh->default_channel_in = talloc_zero(roh, struct roh_channel); + if (tevent_req_nomem(state->roh->default_channel_in, req)) { + return tevent_req_post(req, ev); + } + + state->roh->default_channel_in->send_queue = + tevent_queue_create(state->roh->default_channel_in, + "RoH IN virtual channel send queue"); + if (tevent_req_nomem(state->roh->default_channel_in->send_queue, req)) { + return tevent_req_post(req, ev); + } + + state->roh->default_channel_in->channel_cookie = GUID_random(); + subreq = tstream_inet_tcp_connect_send(state, ev, state->local_address, + state->remote_address); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, roh_connect_channel_in_done, req); + + return req; +} + +static void roh_connect_channel_in_tls_done(struct tevent_req *subreq); +static void roh_connect_channel_in_done(struct tevent_req *subreq) +{ + NTSTATUS status; + struct tevent_req *req; + struct roh_connect_channel_state *state; + int ret; + int sys_errno; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct roh_connect_channel_state); + ret = tstream_inet_tcp_connect_recv(subreq, &sys_errno, state, + &state->roh->default_channel_in->streams.raw, + NULL); + talloc_steal(state->roh->default_channel_in, + state->roh->default_channel_in->streams.raw); + state->roh->default_channel_in->streams.active = state->roh->default_channel_in->streams.raw; + TALLOC_FREE(subreq); + if (ret != 0) { + status = map_nt_error_from_unix_common(sys_errno); + tevent_req_nterror(req, status); + return; + } + + DEBUG(8, ("%s: Socket connected\n", __func__)); + if (state->tls) { + DEBUG(8, ("%s: Starting TLS handshake\n", __func__)); + subreq = _tstream_tls_connect_send(state, + state->ev, + state->roh->default_channel_in->streams.raw, + state->tls_params, + __location__); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, roh_connect_channel_in_tls_done, req); + return; + } + + tevent_req_done(req); +} + +static void roh_connect_channel_in_tls_done(struct tevent_req *subreq) +{ + NTSTATUS status; + struct tevent_req *req; + struct roh_connect_channel_state *state; + int ret; + int sys_errno; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct roh_connect_channel_state); + ret = tstream_tls_connect_recv(subreq, &sys_errno, state, + &state->roh->default_channel_in->streams.tls); + talloc_steal(state->roh->default_channel_in, + state->roh->default_channel_in->streams.tls); + state->roh->default_channel_in->streams.active = state->roh->default_channel_in->streams.tls; + TALLOC_FREE(subreq); + if (ret != 0) { + status = map_nt_error_from_unix_common(sys_errno); + tevent_req_nterror(req, status); + return; + } + DEBUG(8, ("%s: TLS handshake completed\n", __func__)); + + tevent_req_done(req); +} + +NTSTATUS roh_connect_channel_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_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, + bool use_ntlm) +{ + 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->streams.active, + roh->default_channel_in->send_queue, + state->request, + credentials, + lp_ctx, + use_ntlm ? HTTP_AUTH_NTLM : + HTTP_AUTH_BASIC); + 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; + + 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); + 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; + + subreq = tstream_writev_queue_send(mem_ctx, + ev, + roh->default_channel_in->streams.active, + roh->default_channel_in->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 0000000000..b370e564ee --- /dev/null +++ b/source4/librpc/rpc/dcerpc_roh_channel_out.c @@ -0,0 +1,743 @@ +/* + 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 "lib/tevent/tevent.h" +#include "lib/talloc/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 "lib/http/http.h" + +struct roh_connect_channel_state { + struct tevent_context *ev; + struct tsocket_address *local_address; + struct tsocket_address *remote_address; + struct cli_credentials *credentials; + struct roh_connection *roh; + bool tls; + struct tstream_tls_params *tls_params; +}; + +static void roh_connect_channel_out_done(struct tevent_req *subreq); +struct tevent_req *roh_connect_channel_out_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *rpcproxy_ip_address, + unsigned int rpcproxy_port, + struct cli_credentials *credentials, + struct roh_connection *roh, + bool tls, + struct tstream_tls_params *tls_params) +{ + NTSTATUS status; + struct tevent_req *req; + struct tevent_req *subreq; + struct roh_connect_channel_state *state; + int ret; + + DEBUG(8, ("%s: Connecting channel out socket, RPC proxy is %s:%d (TLS: %s)\n", + __func__, 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)) { + DEBUG(0, ("%s: Invalid host (%s), needs to be an IP address\n", + __func__, rpcproxy_ip_address)); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + state->ev = ev; + state->credentials = credentials; + state->roh = roh; + state->tls = tls; + state->tls_params = tls_params; + ret = tsocket_address_inet_from_strings(state, "ip", NULL, 0, + &state->local_address); + if (ret != 0) { + DEBUG(0, ("%s: Cannot create local socket address, error: %s (%d)\n", + __func__, strerror(errno), errno)); + status = map_nt_error_from_unix_common(errno); + tevent_req_nterror(req, status); + return tevent_req_post(req, ev); + } + + ret = tsocket_address_inet_from_strings(state, "ip", + rpcproxy_ip_address, + rpcproxy_port, + &state->remote_address); + if (ret != 0) { + DEBUG(0, ("%s: Cannot create remote socket address, error: %s (%d)\n", + __func__, strerror(errno), errno)); + status = map_nt_error_from_unix_common(errno); + tevent_req_nterror(req, status); + return tevent_req_post(req, ev); + } + + /* Initialize channel structure */ + state->roh->default_channel_out = talloc_zero(roh, struct roh_channel); + if (tevent_req_nomem(state->roh->default_channel_out, req)) { + return tevent_req_post(req, ev); + } + + state->roh->default_channel_out->send_queue = + tevent_queue_create(state->roh->default_channel_out, + "RoH OUT virtual channel send queue"); + if (tevent_req_nomem(state->roh->default_channel_out->send_queue, req)) { + return tevent_req_post(req, ev); + } + + state->roh->default_channel_out->channel_cookie = GUID_random(); + subreq = tstream_inet_tcp_connect_send(state, ev, state->local_address, + state->remote_address); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, roh_connect_channel_out_done, req); + + return req; +} + +static void roh_connect_channel_out_tls_done(struct tevent_req *subreq); +static void roh_connect_channel_out_done(struct tevent_req *subreq) +{ + NTSTATUS status; + struct tevent_req *req; + struct roh_connect_channel_state *state; + int ret; + int sys_errno; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct roh_connect_channel_state); + ret = tstream_inet_tcp_connect_recv(subreq, &sys_errno, state, + &state->roh->default_channel_out->streams.raw, + NULL); + talloc_steal(state->roh->default_channel_out, + state->roh->default_channel_out->streams.raw); + state->roh->default_channel_out->streams.active = state->roh->default_channel_out->streams.raw; + TALLOC_FREE(subreq); + if (ret != 0) { + status = map_nt_error_from_unix_common(sys_errno); + tevent_req_nterror(req, status); + return; + } + + DEBUG(8, ("%s: Socket connected\n", __func__)); + if (state->tls) { + DEBUG(8, ("%s: Starting TLS handshake\n", __func__)); + subreq = _tstream_tls_connect_send(state, + state->ev, + state->roh->default_channel_out->streams.raw, + state->tls_params, + __location__); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, roh_connect_channel_out_tls_done, req); + return; + } + + tevent_req_done(req); +} + +static void roh_connect_channel_out_tls_done(struct tevent_req *subreq) +{ + NTSTATUS status; + struct tevent_req *req; + struct roh_connect_channel_state *state; + int ret; + int sys_errno; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct roh_connect_channel_state); + ret = tstream_tls_connect_recv(subreq, &sys_errno, state, + &state->roh->default_channel_out->streams.tls); + talloc_steal(state->roh->default_channel_out, + state->roh->default_channel_out->streams.tls); + state->roh->default_channel_out->streams.active = state->roh->default_channel_out->streams.tls; + TALLOC_FREE(subreq); + if (ret != 0) { + status = map_nt_error_from_unix_common(sys_errno); + tevent_req_nterror(req, status); + return; + } + DEBUG(8, ("%s: TLS handshake completed\n", __func__)); + + tevent_req_done(req); +} + +NTSTATUS roh_connect_channel_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_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, + bool use_ntlm) +{ + 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->streams.active, + roh->default_channel_out->send_queue, + state->request, + credentials, + lp_ctx, + use_ntlm ? HTTP_AUTH_NTLM : + HTTP_AUTH_BASIC); + 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; + + 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); + 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; + + subreq = tstream_writev_queue_send(mem_ctx, + ev, + roh->default_channel_out->streams.active, + roh->default_channel_out->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->streams.active); + 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; + + 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__)); + subreq = dcerpc_read_ncacn_packet_send(state, ev, + roh->default_channel_out->streams.active); + 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; + + 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__)); + subreq = dcerpc_read_ncacn_packet_send(state, ev, + roh->default_channel_out->streams.active); + 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/wscript_build b/source4/librpc/wscript_build index 582092d514..5b53b6fdeb 100755 --- a/source4/librpc/wscript_build +++ b/source4/librpc/wscript_build @@ -121,9 +121,10 @@ bld.SAMBA_SUBSYSTEM('RPC_NDR_WINSIF', bld.SAMBA_LIBRARY('dcerpc', source='''rpc/dcerpc.c rpc/dcerpc_auth.c rpc/dcerpc_schannel.c rpc/dcerpc_util.c rpc/dcerpc_smb.c rpc/dcerpc_sock.c + rpc/dcerpc_roh_channel_in.c rpc/dcerpc_roh_channel_out.c rpc/dcerpc_roh.c rpc/dcerpc_connect.c rpc/dcerpc_secondary.c''', pc_files='dcerpc.pc', - deps='samba_socket LIBCLI_RESOLVE LIBCLI_SMB LIBCLI_SMB2 ndr NDR_DCERPC RPC_NDR_EPMAPPER NDR_SCHANNEL RPC_NDR_NETLOGON RPC_NDR_MGMT gensec LIBCLI_AUTH smbclient-raw LP_RESOLVE tevent-util dcerpc-binding param_options', + deps='samba_socket LIBCLI_RESOLVE LIBCLI_SMB LIBCLI_SMB2 ndr NDR_DCERPC RPC_NDR_EPMAPPER NDR_SCHANNEL RPC_NDR_NETLOGON RPC_NDR_MGMT gensec LIBCLI_AUTH smbclient-raw LP_RESOLVE tevent-util dcerpc-binding param_options http', allow_warnings=True, autoproto='rpc/dcerpc_proto.h', public_deps='samba-credentials tevent talloc', |