/* Unix SMB/CIFS implementation. Copyright (C) Ralph Boehme 2016 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 . */ #include "includes.h" #include #include "libcli/tstream_binding_handle/tstream_binding_handle.h" #include "system/filesys.h" #include "lib/util/tevent_ntstatus.h" #include "lib/tsocket/tsocket.h" #include "lib/util/debug.h" #include "lib/util/tevent_ntstatus.h" #include "libcli/smb/tstream_smbXcli_np.h" struct tstream_bh_state { struct tstream_context *stream; struct tevent_queue *write_queue; const struct ndr_interface_table *table; uint32_t request_timeout; size_t call_initial_read_size; tstream_read_pdu_blob_full_fn_t *complete_pdu_fn; void *complete_pdu_fn_private; }; static bool tstream_bh_is_connected(struct dcerpc_binding_handle *h) { struct tstream_bh_state *hs = dcerpc_binding_handle_data( h, struct tstream_bh_state); ssize_t ret; if (hs->stream == NULL) { return false; } ret = tstream_pending_bytes(hs->stream); if (ret == -1) { return false; } return true; } static uint32_t tstream_bh_set_timeout(struct dcerpc_binding_handle *h, uint32_t timeout) { struct tstream_bh_state *hs = dcerpc_binding_handle_data( h, struct tstream_bh_state); uint32_t old; old = hs->request_timeout; hs->request_timeout = timeout; return old; } struct tstream_bh_disconnect_state { struct tstream_bh_state *hs; }; static void tstream_bh_disconnect_done(struct tevent_req *subreq); static struct tevent_req *tstream_bh_disconnect_send( TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct dcerpc_binding_handle *h) { struct tstream_bh_state *hs = dcerpc_binding_handle_data( h, struct tstream_bh_state); struct tevent_req *req = NULL; struct tstream_bh_disconnect_state *state = NULL; struct tevent_req *subreq = NULL; bool ok; req = tevent_req_create(mem_ctx, &state, struct tstream_bh_disconnect_state); if (req == NULL) { return NULL; } state->hs = hs; ok = tstream_bh_is_connected(h); if (!ok) { tevent_req_nterror(req, NT_STATUS_CONNECTION_DISCONNECTED); return tevent_req_post(req, ev); } subreq = tstream_disconnect_send(state, ev, hs->stream); if (tevent_req_nomem(subreq, req)) { tevent_req_post(req, ev); return req; } tevent_req_set_callback(subreq, tstream_bh_disconnect_done, req); return req; } static void tstream_bh_disconnect_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct tstream_bh_disconnect_state *state = tevent_req_data( req, struct tstream_bh_disconnect_state); int ret, err; ret = tstream_disconnect_recv(subreq, &err); TALLOC_FREE(subreq); if (ret != 0) { DBG_ERR("tstream_bh_disconnect failed [%s]\n", strerror(err)); tevent_req_nterror(req, map_nt_error_from_unix_common(err)); return; } state->hs->stream = NULL; } static NTSTATUS tstream_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; } struct tstream_bh_call_state { struct tevent_context *ev; struct tstream_context *stream; struct tstream_bh_state *hs; struct iovec out_data; DATA_BLOB in_data; }; static void tstream_bh_call_writev_done(struct tevent_req *subreq); static void tstream_bh_call_read_pdu_done(struct tevent_req *subreq); static struct tevent_req *tstream_bh_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 *out_data, size_t out_length) { struct tstream_bh_state *hs = dcerpc_binding_handle_data( h, struct tstream_bh_state); struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct tstream_bh_call_state* state = NULL; struct timeval timeout; bool ok; req = tevent_req_create(mem_ctx, &state, struct tstream_bh_call_state); if (req == NULL) { return NULL; } *state = (struct tstream_bh_call_state) { .ev = ev, .stream = hs->stream, .hs = hs, .out_data = { .iov_base = discard_const_p(uint8_t, out_data), .iov_len = out_length, }, }; ok = tstream_bh_is_connected(h); if (!ok) { tevent_req_nterror(req, NT_STATUS_CONNECTION_DISCONNECTED); return tevent_req_post(req, ev); } if (tstream_is_smbXcli_np(hs->stream)) { tstream_smbXcli_np_use_trans(hs->stream); } if (tevent_queue_length(hs->write_queue) > 0) { tevent_req_nterror(req, NT_STATUS_PIPE_BUSY); } timeout = timeval_current_ofs(hs->request_timeout, 0); subreq = tstream_writev_queue_send(state, ev, state->stream, hs->write_queue, &state->out_data, 1); if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, ev); } if (!tevent_req_set_endtime(subreq, ev, timeout)) { return tevent_req_post(req, ev); } tevent_req_set_callback(subreq, tstream_bh_call_writev_done, req); subreq = tstream_read_pdu_blob_send(state, ev, hs->stream, hs->call_initial_read_size, hs->complete_pdu_fn, hs->complete_pdu_fn_private); if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, ev); } if (!tevent_req_set_endtime(subreq, ev, timeout)) { return tevent_req_post(req, ev); } tevent_req_set_callback(subreq, tstream_bh_call_read_pdu_done, req); return req; } static void tstream_bh_call_writev_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct tstream_bh_call_state *state = tevent_req_data( req, struct tstream_bh_call_state); int ret, err; ret = tstream_writev_queue_recv(subreq, &err); TALLOC_FREE(subreq); if (ret == -1) { state->hs->stream = NULL; tevent_req_nterror(req, map_nt_error_from_unix_common(err)); return; } } static void tstream_bh_call_read_pdu_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct tstream_bh_call_state *state = tevent_req_data( req, struct tstream_bh_call_state); NTSTATUS status; status = tstream_read_pdu_blob_recv(subreq, state, &state->in_data); TALLOC_FREE(subreq); if (!NT_STATUS_IS_OK(status)) { state->hs->stream = NULL; tevent_req_nterror(req, status); return; } tevent_req_done(req); } static NTSTATUS tstream_bh_call_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, uint8_t **in_data, size_t *in_length, uint32_t *in_flags) { NTSTATUS status; struct tstream_bh_call_state *state = tevent_req_data( req, struct tstream_bh_call_state); if (tevent_req_is_nterror(req, &status)) { tevent_req_received(req); return status; } *in_data = talloc_move(mem_ctx, &state->in_data.data); *in_length = state->in_data.length; tevent_req_received(req); return NT_STATUS_OK; } static const struct dcerpc_binding_handle_ops tstream_bh_ops = { .name = "tstream_binding_handle", .is_connected = tstream_bh_is_connected, .set_timeout = tstream_bh_set_timeout, .raw_call_send = tstream_bh_call_send, .raw_call_recv = tstream_bh_call_recv, .disconnect_send = tstream_bh_disconnect_send, .disconnect_recv = tstream_bh_disconnect_recv, }; struct dcerpc_binding_handle *tstream_binding_handle_create( TALLOC_CTX *mem_ctx, const struct ndr_interface_table *table, struct tstream_context **stream, size_t call_initial_read_size, tstream_read_pdu_blob_full_fn_t *complete_pdu_fn, void *complete_pdu_fn_private, uint32_t max_data) { struct dcerpc_binding_handle *h = NULL; struct tstream_bh_state *hs = NULL; h = dcerpc_binding_handle_create(mem_ctx, &tstream_bh_ops, NULL, table, &hs, struct tstream_bh_state, __location__); if (h == NULL) { return NULL; } hs->table = table; hs->stream = talloc_move(hs, stream); hs->call_initial_read_size = call_initial_read_size; hs->complete_pdu_fn = complete_pdu_fn; hs->complete_pdu_fn_private = complete_pdu_fn_private; hs->write_queue = tevent_queue_create(hs, "write_queue"); if (hs->write_queue == NULL) { TALLOC_FREE(h); return NULL; } if (max_data > 0) { tstream_smbXcli_np_set_max_data(hs->stream, max_data); } return h; }