diff options
Diffstat (limited to '')
-rw-r--r-- | libcli/smb/tstream_smbXcli_np.c | 1384 |
1 files changed, 1384 insertions, 0 deletions
diff --git a/libcli/smb/tstream_smbXcli_np.c b/libcli/smb/tstream_smbXcli_np.c new file mode 100644 index 0000000..8dfc4fb --- /dev/null +++ b/libcli/smb/tstream_smbXcli_np.c @@ -0,0 +1,1384 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2010 + + 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/network.h" +#include "../lib/util/tevent_ntstatus.h" +#include "../lib/tsocket/tsocket.h" +#include "../lib/tsocket/tsocket_internal.h" +#include "smb_common.h" +#include "smbXcli_base.h" +#include "tstream_smbXcli_np.h" +#include "libcli/security/security.h" + +static const struct tstream_context_ops tstream_smbXcli_np_ops; + +#define TSTREAM_SMBXCLI_NP_DESIRED_ACCESS ( \ + SEC_STD_READ_CONTROL | \ + SEC_FILE_READ_DATA | \ + SEC_FILE_WRITE_DATA | \ + SEC_FILE_APPEND_DATA | \ + SEC_FILE_READ_EA | \ + SEC_FILE_WRITE_EA | \ + SEC_FILE_READ_ATTRIBUTE | \ + SEC_FILE_WRITE_ATTRIBUTE | \ +0) + +struct tstream_smbXcli_np_ref; + +struct tstream_smbXcli_np { + struct smbXcli_conn *conn; + struct tstream_smbXcli_np_ref *conn_ref; + struct smbXcli_session *session; + struct tstream_smbXcli_np_ref *session_ref; + struct smbXcli_tcon *tcon; + struct tstream_smbXcli_np_ref *tcon_ref; + uint16_t pid; + unsigned int timeout; + + const char *npipe; + bool is_smb1; + uint16_t fnum; + uint64_t fid_persistent; + uint64_t fid_volatile; + + struct { + bool active; + struct tevent_req *read_req; + struct tevent_req *write_req; + uint16_t setup[2]; + } trans; + + struct { + off_t ofs; + size_t left; + uint8_t *buf; + } read, write; +}; + +struct tstream_smbXcli_np_ref { + struct tstream_smbXcli_np *cli_nps; +}; + +static int tstream_smbXcli_np_destructor(struct tstream_smbXcli_np *cli_nps) +{ + NTSTATUS status; + + if (cli_nps->conn_ref != NULL) { + cli_nps->conn_ref->cli_nps = NULL; + TALLOC_FREE(cli_nps->conn_ref); + } + + if (cli_nps->session_ref != NULL) { + cli_nps->session_ref->cli_nps = NULL; + TALLOC_FREE(cli_nps->session_ref); + } + + if (cli_nps->tcon_ref != NULL) { + cli_nps->tcon_ref->cli_nps = NULL; + TALLOC_FREE(cli_nps->tcon_ref); + } + + if (!smbXcli_conn_is_connected(cli_nps->conn)) { + return 0; + } + + /* + * TODO: do not use a sync call with a destructor!!! + * + * This only happens, if a caller does talloc_free(), + * while the everything was still ok. + * + * If we get an unexpected failure within a normal + * operation, we already do an async cli_close_send()/_recv(). + * + * Once we've fixed all callers to call + * tstream_disconnect_send()/_recv(), this will + * never be called. + * + * We use a maximun timeout of 1 second == 1000 msec. + */ + cli_nps->timeout = MIN(cli_nps->timeout, 1000); + + if (cli_nps->is_smb1) { + status = smb1cli_close(cli_nps->conn, + cli_nps->timeout, + cli_nps->pid, + cli_nps->tcon, + cli_nps->session, + cli_nps->fnum, UINT32_MAX); + } else { + status = smb2cli_close(cli_nps->conn, + cli_nps->timeout, + cli_nps->session, + cli_nps->tcon, + 0, /* flags */ + cli_nps->fid_persistent, + cli_nps->fid_volatile); + } + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("tstream_smbXcli_np_destructor: cli_close " + "failed on pipe %s. Error was %s\n", + cli_nps->npipe, nt_errstr(status))); + } + /* + * We can't do much on failure + */ + return 0; +} + +static int tstream_smbXcli_np_ref_destructor(struct tstream_smbXcli_np_ref *ref) +{ + if (ref->cli_nps == NULL) { + return 0; + } + + if (ref->cli_nps->conn == NULL) { + return 0; + } + + ref->cli_nps->conn = NULL; + ref->cli_nps->session = NULL; + ref->cli_nps->tcon = NULL; + + TALLOC_FREE(ref->cli_nps->conn_ref); + TALLOC_FREE(ref->cli_nps->session_ref); + TALLOC_FREE(ref->cli_nps->tcon_ref); + + return 0; +}; + +static struct tevent_req *tstream_smbXcli_np_disconnect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *stream); +static int tstream_smbXcli_np_disconnect_recv(struct tevent_req *req, + int *perrno); + +struct tstream_smbXcli_np_open_state { + struct smbXcli_conn *conn; + struct smbXcli_session *session; + struct smbXcli_tcon *tcon; + uint16_t pid; + unsigned int timeout; + + bool is_smb1; + uint16_t fnum; + uint64_t fid_persistent; + uint64_t fid_volatile; + const char *npipe; +}; + +static void tstream_smbXcli_np_open_done(struct tevent_req *subreq); + +struct tevent_req *tstream_smbXcli_np_open_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint16_t pid, + unsigned int timeout, + const char *npipe) +{ + struct tevent_req *req; + struct tstream_smbXcli_np_open_state *state; + struct tevent_req *subreq; + + req = tevent_req_create(mem_ctx, &state, + struct tstream_smbXcli_np_open_state); + if (!req) { + return NULL; + } + state->conn = conn; + state->tcon = tcon; + state->session = session; + state->pid = pid; + state->timeout = timeout; + + state->npipe = talloc_strdup(state, npipe); + if (tevent_req_nomem(state->npipe, req)) { + return tevent_req_post(req, ev); + } + + if (smbXcli_conn_protocol(conn) < PROTOCOL_SMB2_02) { + state->is_smb1 = true; + } + + if (state->is_smb1) { + const char *smb1_npipe; + + /* + * Windows and newer Samba versions allow + * the pipe name without leading backslash, + * but we should better behave like windows clients + */ + smb1_npipe = talloc_asprintf(state, "\\%s", state->npipe); + if (tevent_req_nomem(smb1_npipe, req)) { + return tevent_req_post(req, ev); + } + subreq = smb1cli_ntcreatex_send(state, ev, state->conn, + state->timeout, + state->pid, + state->tcon, + state->session, + smb1_npipe, + 0, /* CreatFlags */ + 0, /* RootDirectoryFid */ + TSTREAM_SMBXCLI_NP_DESIRED_ACCESS, + 0, /* AllocationSize */ + 0, /* FileAttributes */ + FILE_SHARE_READ|FILE_SHARE_WRITE, + FILE_OPEN, /* CreateDisposition */ + 0, /* CreateOptions */ + 2, /* NTCREATEX_IMPERSONATION_IMPERSONATION */ + 0); /* SecurityFlags */ + } else { + subreq = smb2cli_create_send(state, ev, state->conn, + state->timeout, state->session, + state->tcon, + npipe, + SMB2_OPLOCK_LEVEL_NONE, + SMB2_IMPERSONATION_IMPERSONATION, + TSTREAM_SMBXCLI_NP_DESIRED_ACCESS, + 0, /* file_attributes */ + FILE_SHARE_READ|FILE_SHARE_WRITE, + FILE_OPEN, + 0, /* create_options */ + NULL); /* blobs */ + } + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, tstream_smbXcli_np_open_done, req); + + return req; +} + +static void tstream_smbXcli_np_open_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct tstream_smbXcli_np_open_state *state = + tevent_req_data(req, struct tstream_smbXcli_np_open_state); + NTSTATUS status; + + if (state->is_smb1) { + status = smb1cli_ntcreatex_recv(subreq, &state->fnum); + } else { + status = smb2cli_create_recv(subreq, + &state->fid_persistent, + &state->fid_volatile, + NULL, NULL, NULL); + } + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return; + } + + tevent_req_done(req); +} + +NTSTATUS _tstream_smbXcli_np_open_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct tstream_context **_stream, + const char *location) +{ + struct tstream_smbXcli_np_open_state *state = + tevent_req_data(req, struct tstream_smbXcli_np_open_state); + struct tstream_context *stream; + struct tstream_smbXcli_np *cli_nps; + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + stream = tstream_context_create(mem_ctx, + &tstream_smbXcli_np_ops, + &cli_nps, + struct tstream_smbXcli_np, + location); + if (!stream) { + tevent_req_received(req); + return NT_STATUS_NO_MEMORY; + } + ZERO_STRUCTP(cli_nps); + + cli_nps->conn_ref = talloc_zero(state->conn, + struct tstream_smbXcli_np_ref); + if (cli_nps->conn_ref == NULL) { + TALLOC_FREE(cli_nps); + tevent_req_received(req); + return NT_STATUS_NO_MEMORY; + } + cli_nps->conn_ref->cli_nps = cli_nps; + + cli_nps->session_ref = talloc_zero(state->session, + struct tstream_smbXcli_np_ref); + if (cli_nps->session_ref == NULL) { + TALLOC_FREE(cli_nps); + tevent_req_received(req); + return NT_STATUS_NO_MEMORY; + } + cli_nps->session_ref->cli_nps = cli_nps; + + cli_nps->tcon_ref = talloc_zero(state->tcon, + struct tstream_smbXcli_np_ref); + if (cli_nps->tcon_ref == NULL) { + TALLOC_FREE(cli_nps); + tevent_req_received(req); + return NT_STATUS_NO_MEMORY; + } + cli_nps->tcon_ref->cli_nps = cli_nps; + + cli_nps->conn = state->conn; + cli_nps->session = state->session; + cli_nps->tcon = state->tcon; + cli_nps->pid = state->pid; + cli_nps->timeout = state->timeout; + cli_nps->npipe = talloc_move(cli_nps, &state->npipe); + cli_nps->is_smb1 = state->is_smb1; + cli_nps->fnum = state->fnum; + cli_nps->fid_persistent = state->fid_persistent; + cli_nps->fid_volatile = state->fid_volatile; + + talloc_set_destructor(cli_nps, tstream_smbXcli_np_destructor); + talloc_set_destructor(cli_nps->conn_ref, + tstream_smbXcli_np_ref_destructor); + talloc_set_destructor(cli_nps->session_ref, + tstream_smbXcli_np_ref_destructor); + talloc_set_destructor(cli_nps->tcon_ref, + tstream_smbXcli_np_ref_destructor); + + cli_nps->trans.active = false; + cli_nps->trans.read_req = NULL; + cli_nps->trans.write_req = NULL; + SSVAL(cli_nps->trans.setup+0, 0, TRANSACT_DCERPCCMD); + SSVAL(cli_nps->trans.setup+1, 0, cli_nps->fnum); + + *_stream = stream; + tevent_req_received(req); + return NT_STATUS_OK; +} + +static ssize_t tstream_smbXcli_np_pending_bytes(struct tstream_context *stream) +{ + struct tstream_smbXcli_np *cli_nps = tstream_context_data(stream, + struct tstream_smbXcli_np); + + if (!smbXcli_conn_is_connected(cli_nps->conn)) { + errno = ENOTCONN; + return -1; + } + + return cli_nps->read.left; +} + +bool tstream_is_smbXcli_np(struct tstream_context *stream) +{ + struct tstream_smbXcli_np *cli_nps = + talloc_get_type(_tstream_context_data(stream), + struct tstream_smbXcli_np); + + if (!cli_nps) { + return false; + } + + return true; +} + +NTSTATUS tstream_smbXcli_np_use_trans(struct tstream_context *stream) +{ + struct tstream_smbXcli_np *cli_nps = tstream_context_data(stream, + struct tstream_smbXcli_np); + + if (cli_nps->trans.read_req) { + return NT_STATUS_PIPE_BUSY; + } + + if (cli_nps->trans.write_req) { + return NT_STATUS_PIPE_BUSY; + } + + if (cli_nps->trans.active) { + return NT_STATUS_PIPE_BUSY; + } + + cli_nps->trans.active = true; + + return NT_STATUS_OK; +} + +unsigned int tstream_smbXcli_np_set_timeout(struct tstream_context *stream, + unsigned int timeout) +{ + struct tstream_smbXcli_np *cli_nps = tstream_context_data(stream, + struct tstream_smbXcli_np); + unsigned int old_timeout = cli_nps->timeout; + + cli_nps->timeout = timeout; + return old_timeout; +} + +struct tstream_smbXcli_np_writev_state { + struct tstream_context *stream; + struct tevent_context *ev; + + struct iovec *vector; + size_t count; + + int ret; + + struct { + int val; + const char *location; + } error; +}; + +static int tstream_smbXcli_np_writev_state_destructor(struct tstream_smbXcli_np_writev_state *state) +{ + struct tstream_smbXcli_np *cli_nps = + tstream_context_data(state->stream, + struct tstream_smbXcli_np); + + cli_nps->trans.write_req = NULL; + + return 0; +} + +static void tstream_smbXcli_np_writev_write_next(struct tevent_req *req); + +static struct tevent_req *tstream_smbXcli_np_writev_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *stream, + const struct iovec *vector, + size_t count) +{ + struct tevent_req *req; + struct tstream_smbXcli_np_writev_state *state; + struct tstream_smbXcli_np *cli_nps = tstream_context_data(stream, + struct tstream_smbXcli_np); + + req = tevent_req_create(mem_ctx, &state, + struct tstream_smbXcli_np_writev_state); + if (!req) { + return NULL; + } + state->stream = stream; + state->ev = ev; + state->ret = 0; + + talloc_set_destructor(state, tstream_smbXcli_np_writev_state_destructor); + + if (!smbXcli_conn_is_connected(cli_nps->conn)) { + tevent_req_error(req, ENOTCONN); + return tevent_req_post(req, ev); + } + + /* + * we make a copy of the vector so we can change the structure + */ + state->vector = talloc_array(state, struct iovec, count); + if (tevent_req_nomem(state->vector, req)) { + return tevent_req_post(req, ev); + } + memcpy(state->vector, vector, sizeof(struct iovec) * count); + state->count = count; + + tstream_smbXcli_np_writev_write_next(req); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void tstream_smbXcli_np_readv_trans_start(struct tevent_req *req); +static void tstream_smbXcli_np_writev_write_done(struct tevent_req *subreq); + +static void tstream_smbXcli_np_writev_write_next(struct tevent_req *req) +{ + struct tstream_smbXcli_np_writev_state *state = + tevent_req_data(req, + struct tstream_smbXcli_np_writev_state); + struct tstream_smbXcli_np *cli_nps = + tstream_context_data(state->stream, + struct tstream_smbXcli_np); + struct tevent_req *subreq; + size_t i; + size_t left = 0; + + for (i=0; i < state->count; i++) { + left += state->vector[i].iov_len; + } + + if (left == 0) { + TALLOC_FREE(cli_nps->write.buf); + tevent_req_done(req); + return; + } + + cli_nps->write.ofs = 0; + cli_nps->write.left = MIN(left, TSTREAM_SMBXCLI_NP_MAX_BUF_SIZE); + cli_nps->write.buf = talloc_realloc(cli_nps, cli_nps->write.buf, + uint8_t, cli_nps->write.left); + if (tevent_req_nomem(cli_nps->write.buf, req)) { + return; + } + + /* + * copy the pending buffer first + */ + while (cli_nps->write.left > 0 && state->count > 0) { + uint8_t *base = (uint8_t *)state->vector[0].iov_base; + size_t len = MIN(cli_nps->write.left, state->vector[0].iov_len); + + memcpy(cli_nps->write.buf + cli_nps->write.ofs, base, len); + + base += len; + state->vector[0].iov_base = base; + state->vector[0].iov_len -= len; + + cli_nps->write.ofs += len; + cli_nps->write.left -= len; + + if (state->vector[0].iov_len == 0) { + state->vector += 1; + state->count -= 1; + } + + state->ret += len; + } + + if (cli_nps->trans.active && state->count == 0) { + cli_nps->trans.active = false; + cli_nps->trans.write_req = req; + return; + } + + if (cli_nps->trans.read_req && state->count == 0) { + cli_nps->trans.write_req = req; + tstream_smbXcli_np_readv_trans_start(cli_nps->trans.read_req); + return; + } + + if (cli_nps->is_smb1) { + subreq = smb1cli_writex_send(state, state->ev, + cli_nps->conn, + cli_nps->timeout, + cli_nps->pid, + cli_nps->tcon, + cli_nps->session, + cli_nps->fnum, + 8, /* 8 means message mode. */ + cli_nps->write.buf, + 0, /* offset */ + cli_nps->write.ofs); /* size */ + } else { + subreq = smb2cli_write_send(state, state->ev, + cli_nps->conn, + cli_nps->timeout, + cli_nps->session, + cli_nps->tcon, + cli_nps->write.ofs, /* length */ + 0, /* offset */ + cli_nps->fid_persistent, + cli_nps->fid_volatile, + 0, /* remaining_bytes */ + 0, /* flags */ + cli_nps->write.buf); + } + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, + tstream_smbXcli_np_writev_write_done, + req); +} + +static void tstream_smbXcli_np_writev_disconnect_now(struct tevent_req *req, + int error, + const char *location); + +static void tstream_smbXcli_np_writev_write_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct tstream_smbXcli_np_writev_state *state = + tevent_req_data(req, struct tstream_smbXcli_np_writev_state); + struct tstream_smbXcli_np *cli_nps = + tstream_context_data(state->stream, + struct tstream_smbXcli_np); + uint32_t written; + NTSTATUS status; + + if (cli_nps->is_smb1) { + status = smb1cli_writex_recv(subreq, &written, NULL); + } else { + status = smb2cli_write_recv(subreq, &written); + } + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + tstream_smbXcli_np_writev_disconnect_now(req, EPIPE, __location__); + return; + } + + if (written != cli_nps->write.ofs) { + tstream_smbXcli_np_writev_disconnect_now(req, EIO, __location__); + return; + } + + tstream_smbXcli_np_writev_write_next(req); +} + +static void tstream_smbXcli_np_writev_disconnect_done(struct tevent_req *subreq); + +static void tstream_smbXcli_np_writev_disconnect_now(struct tevent_req *req, + int error, + const char *location) +{ + struct tstream_smbXcli_np_writev_state *state = + tevent_req_data(req, + struct tstream_smbXcli_np_writev_state); + struct tstream_smbXcli_np *cli_nps = + tstream_context_data(state->stream, + struct tstream_smbXcli_np); + struct tevent_req *subreq; + + state->error.val = error; + state->error.location = location; + + if (!smbXcli_conn_is_connected(cli_nps->conn)) { + /* return the original error */ + _tevent_req_error(req, state->error.val, state->error.location); + return; + } + + subreq = tstream_smbXcli_np_disconnect_send(state, state->ev, + state->stream); + if (subreq == NULL) { + /* return the original error */ + _tevent_req_error(req, state->error.val, state->error.location); + return; + } + tevent_req_set_callback(subreq, + tstream_smbXcli_np_writev_disconnect_done, + req); +} + +static void tstream_smbXcli_np_writev_disconnect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct tstream_smbXcli_np_writev_state *state = + tevent_req_data(req, struct tstream_smbXcli_np_writev_state); + int error; + + tstream_smbXcli_np_disconnect_recv(subreq, &error); + TALLOC_FREE(subreq); + + /* return the original error */ + _tevent_req_error(req, state->error.val, state->error.location); +} + +static int tstream_smbXcli_np_writev_recv(struct tevent_req *req, + int *perrno) +{ + struct tstream_smbXcli_np_writev_state *state = + tevent_req_data(req, + struct tstream_smbXcli_np_writev_state); + int ret; + + ret = tsocket_simple_int_recv(req, perrno); + if (ret == 0) { + ret = state->ret; + } + + tevent_req_received(req); + return ret; +} + +struct tstream_smbXcli_np_readv_state { + struct tstream_context *stream; + struct tevent_context *ev; + + struct iovec *vector; + size_t count; + + int ret; + + struct { + struct tevent_immediate *im; + } trans; + + struct { + int val; + const char *location; + } error; +}; + +static int tstream_smbXcli_np_readv_state_destructor(struct tstream_smbXcli_np_readv_state *state) +{ + struct tstream_smbXcli_np *cli_nps = + tstream_context_data(state->stream, + struct tstream_smbXcli_np); + + cli_nps->trans.read_req = NULL; + + return 0; +} + +static void tstream_smbXcli_np_readv_read_next(struct tevent_req *req); + +static struct tevent_req *tstream_smbXcli_np_readv_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *stream, + struct iovec *vector, + size_t count) +{ + struct tevent_req *req; + struct tstream_smbXcli_np_readv_state *state; + struct tstream_smbXcli_np *cli_nps = + tstream_context_data(stream, struct tstream_smbXcli_np); + + req = tevent_req_create(mem_ctx, &state, + struct tstream_smbXcli_np_readv_state); + if (!req) { + return NULL; + } + state->stream = stream; + state->ev = ev; + state->ret = 0; + + talloc_set_destructor(state, tstream_smbXcli_np_readv_state_destructor); + + if (!smbXcli_conn_is_connected(cli_nps->conn)) { + tevent_req_error(req, ENOTCONN); + return tevent_req_post(req, ev); + } + + /* + * we make a copy of the vector so we can change the structure + */ + state->vector = talloc_array(state, struct iovec, count); + if (tevent_req_nomem(state->vector, req)) { + return tevent_req_post(req, ev); + } + memcpy(state->vector, vector, sizeof(struct iovec) * count); + state->count = count; + + tstream_smbXcli_np_readv_read_next(req); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void tstream_smbXcli_np_readv_read_done(struct tevent_req *subreq); + +static void tstream_smbXcli_np_readv_read_next(struct tevent_req *req) +{ + struct tstream_smbXcli_np_readv_state *state = + tevent_req_data(req, + struct tstream_smbXcli_np_readv_state); + struct tstream_smbXcli_np *cli_nps = + tstream_context_data(state->stream, + struct tstream_smbXcli_np); + struct tevent_req *subreq; + + /* + * copy the pending buffer first + */ + while (cli_nps->read.left > 0 && state->count > 0) { + uint8_t *base = (uint8_t *)state->vector[0].iov_base; + size_t len = MIN(cli_nps->read.left, state->vector[0].iov_len); + + memcpy(base, cli_nps->read.buf + cli_nps->read.ofs, len); + + base += len; + state->vector[0].iov_base = base; + state->vector[0].iov_len -= len; + + cli_nps->read.ofs += len; + cli_nps->read.left -= len; + + if (state->vector[0].iov_len == 0) { + state->vector += 1; + state->count -= 1; + } + + state->ret += len; + } + + if (cli_nps->read.left == 0) { + TALLOC_FREE(cli_nps->read.buf); + } + + if (state->count == 0) { + tevent_req_done(req); + return; + } + + if (cli_nps->trans.active) { + cli_nps->trans.active = false; + cli_nps->trans.read_req = req; + return; + } + + if (cli_nps->trans.write_req) { + cli_nps->trans.read_req = req; + tstream_smbXcli_np_readv_trans_start(req); + return; + } + + if (cli_nps->is_smb1) { + subreq = smb1cli_readx_send(state, state->ev, + cli_nps->conn, + cli_nps->timeout, + cli_nps->pid, + cli_nps->tcon, + cli_nps->session, + cli_nps->fnum, + 0, /* offset */ + TSTREAM_SMBXCLI_NP_MAX_BUF_SIZE); + } else { + subreq = smb2cli_read_send(state, state->ev, + cli_nps->conn, + cli_nps->timeout, + cli_nps->session, + cli_nps->tcon, + TSTREAM_SMBXCLI_NP_MAX_BUF_SIZE, /* length */ + 0, /* offset */ + cli_nps->fid_persistent, + cli_nps->fid_volatile, + 0, /* minimum_count */ + 0); /* remaining_bytes */ + } + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, + tstream_smbXcli_np_readv_read_done, + req); +} + +static void tstream_smbXcli_np_readv_trans_done(struct tevent_req *subreq); + +static void tstream_smbXcli_np_readv_trans_start(struct tevent_req *req) +{ + struct tstream_smbXcli_np_readv_state *state = + tevent_req_data(req, + struct tstream_smbXcli_np_readv_state); + struct tstream_smbXcli_np *cli_nps = + tstream_context_data(state->stream, + struct tstream_smbXcli_np); + struct tevent_req *subreq; + + state->trans.im = tevent_create_immediate(state); + if (tevent_req_nomem(state->trans.im, req)) { + return; + } + + if (cli_nps->is_smb1) { + subreq = smb1cli_trans_send(state, state->ev, + cli_nps->conn, SMBtrans, + 0, 0, /* *_flags */ + 0, 0, /* *_flags2 */ + cli_nps->timeout, + cli_nps->pid, + cli_nps->tcon, + cli_nps->session, + "\\PIPE\\", + 0, 0, 0, + cli_nps->trans.setup, 2, + 0, + NULL, 0, 0, + cli_nps->write.buf, + cli_nps->write.ofs, + TSTREAM_SMBXCLI_NP_MAX_BUF_SIZE); + } else { + DATA_BLOB in_input_buffer = data_blob_null; + DATA_BLOB in_output_buffer = data_blob_null; + + in_input_buffer = data_blob_const(cli_nps->write.buf, + cli_nps->write.ofs); + + subreq = smb2cli_ioctl_send(state, state->ev, + cli_nps->conn, + cli_nps->timeout, + cli_nps->session, + cli_nps->tcon, + cli_nps->fid_persistent, + cli_nps->fid_volatile, + FSCTL_NAMED_PIPE_READ_WRITE, + 0, /* in_max_input_length */ + &in_input_buffer, + /* in_max_output_length */ + TSTREAM_SMBXCLI_NP_MAX_BUF_SIZE, + &in_output_buffer, + SMB2_IOCTL_FLAG_IS_FSCTL); + } + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, + tstream_smbXcli_np_readv_trans_done, + req); +} + +static void tstream_smbXcli_np_readv_disconnect_now(struct tevent_req *req, + int error, + const char *location); +static void tstream_smbXcli_np_readv_trans_next(struct tevent_context *ctx, + struct tevent_immediate *im, + void *private_data); + +static void tstream_smbXcli_np_readv_trans_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct tstream_smbXcli_np_readv_state *state = + tevent_req_data(req, struct tstream_smbXcli_np_readv_state); + struct tstream_smbXcli_np *cli_nps = + tstream_context_data(state->stream, struct tstream_smbXcli_np); + uint8_t *rcvbuf; + uint32_t received; + NTSTATUS status; + + if (cli_nps->is_smb1) { + status = smb1cli_trans_recv(subreq, state, NULL, NULL, 0, NULL, + NULL, 0, NULL, + &rcvbuf, 0, &received); + } else { + DATA_BLOB out_input_buffer = data_blob_null; + DATA_BLOB out_output_buffer = data_blob_null; + + status = smb2cli_ioctl_recv(subreq, state, + &out_input_buffer, + &out_output_buffer); + + /* Note that rcvbuf is not a talloc pointer here */ + rcvbuf = out_output_buffer.data; + received = out_output_buffer.length; + } + TALLOC_FREE(subreq); + if (NT_STATUS_EQUAL(status, STATUS_BUFFER_OVERFLOW)) { + /* + * STATUS_BUFFER_OVERFLOW means that there's + * more data to read when the named pipe is used + * in message mode (which is the case here). + * + * But we hide this from the caller. + */ + status = NT_STATUS_OK; + } + if (!NT_STATUS_IS_OK(status)) { + tstream_smbXcli_np_readv_disconnect_now(req, EPIPE, __location__); + return; + } + + if (received > TSTREAM_SMBXCLI_NP_MAX_BUF_SIZE) { + tstream_smbXcli_np_readv_disconnect_now(req, EIO, __location__); + return; + } + + if (received == 0) { + tstream_smbXcli_np_readv_disconnect_now(req, EPIPE, __location__); + return; + } + + cli_nps->read.ofs = 0; + cli_nps->read.left = received; + cli_nps->read.buf = talloc_array(cli_nps, uint8_t, received); + if (cli_nps->read.buf == NULL) { + TALLOC_FREE(subreq); + tevent_req_oom(req); + return; + } + memcpy(cli_nps->read.buf, rcvbuf, received); + + if (cli_nps->trans.write_req == NULL) { + tstream_smbXcli_np_readv_read_next(req); + return; + } + + tevent_schedule_immediate(state->trans.im, state->ev, + tstream_smbXcli_np_readv_trans_next, req); + + tevent_req_done(cli_nps->trans.write_req); +} + +static void tstream_smbXcli_np_readv_trans_next(struct tevent_context *ctx, + struct tevent_immediate *im, + void *private_data) +{ + struct tevent_req *req = + talloc_get_type_abort(private_data, + struct tevent_req); + + tstream_smbXcli_np_readv_read_next(req); +} + +static void tstream_smbXcli_np_readv_read_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct tstream_smbXcli_np_readv_state *state = + tevent_req_data(req, struct tstream_smbXcli_np_readv_state); + struct tstream_smbXcli_np *cli_nps = + tstream_context_data(state->stream, struct tstream_smbXcli_np); + uint8_t *rcvbuf; + uint32_t received; + NTSTATUS status; + + /* + * We must free subreq in this function as there is + * a timer event attached to it. + */ + + if (cli_nps->is_smb1) { + status = smb1cli_readx_recv(subreq, &received, &rcvbuf); + } else { + status = smb2cli_read_recv(subreq, state, &rcvbuf, &received); + } + /* + * We can't TALLOC_FREE(subreq) as usual here, as rcvbuf still is a + * child of that. + */ + if (NT_STATUS_EQUAL(status, STATUS_BUFFER_OVERFLOW)) { + /* + * STATUS_BUFFER_OVERFLOW means that there's + * more data to read when the named pipe is used + * in message mode (which is the case here). + * + * But we hide this from the caller. + */ + status = NT_STATUS_OK; + } + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(subreq); + tstream_smbXcli_np_readv_disconnect_now(req, EPIPE, __location__); + return; + } + + if (received > TSTREAM_SMBXCLI_NP_MAX_BUF_SIZE) { + TALLOC_FREE(subreq); + tstream_smbXcli_np_readv_disconnect_now(req, EIO, __location__); + return; + } + + if (received == 0) { + TALLOC_FREE(subreq); + tstream_smbXcli_np_readv_disconnect_now(req, EPIPE, __location__); + return; + } + + cli_nps->read.ofs = 0; + cli_nps->read.left = received; + cli_nps->read.buf = talloc_array(cli_nps, uint8_t, received); + if (cli_nps->read.buf == NULL) { + TALLOC_FREE(subreq); + tevent_req_oom(req); + return; + } + memcpy(cli_nps->read.buf, rcvbuf, received); + TALLOC_FREE(subreq); + + tstream_smbXcli_np_readv_read_next(req); +} + +static void tstream_smbXcli_np_readv_disconnect_done(struct tevent_req *subreq); + +static void tstream_smbXcli_np_readv_error(struct tevent_req *req); + +static void tstream_smbXcli_np_readv_disconnect_now(struct tevent_req *req, + int error, + const char *location) +{ + struct tstream_smbXcli_np_readv_state *state = + tevent_req_data(req, + struct tstream_smbXcli_np_readv_state); + struct tstream_smbXcli_np *cli_nps = + tstream_context_data(state->stream, + struct tstream_smbXcli_np); + struct tevent_req *subreq; + + state->error.val = error; + state->error.location = location; + + if (!smbXcli_conn_is_connected(cli_nps->conn)) { + /* return the original error */ + tstream_smbXcli_np_readv_error(req); + return; + } + + subreq = tstream_smbXcli_np_disconnect_send(state, state->ev, + state->stream); + if (subreq == NULL) { + /* return the original error */ + tstream_smbXcli_np_readv_error(req); + return; + } + tevent_req_set_callback(subreq, + tstream_smbXcli_np_readv_disconnect_done, + req); +} + +static void tstream_smbXcli_np_readv_disconnect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + int error; + + tstream_smbXcli_np_disconnect_recv(subreq, &error); + TALLOC_FREE(subreq); + + tstream_smbXcli_np_readv_error(req); +} + +static void tstream_smbXcli_np_readv_error_trigger(struct tevent_context *ctx, + struct tevent_immediate *im, + void *private_data); + +static void tstream_smbXcli_np_readv_error(struct tevent_req *req) +{ + struct tstream_smbXcli_np_readv_state *state = + tevent_req_data(req, + struct tstream_smbXcli_np_readv_state); + struct tstream_smbXcli_np *cli_nps = + tstream_context_data(state->stream, + struct tstream_smbXcli_np); + + if (cli_nps->trans.write_req == NULL) { + /* return the original error */ + _tevent_req_error(req, state->error.val, state->error.location); + return; + } + + if (state->trans.im == NULL) { + /* return the original error */ + _tevent_req_error(req, state->error.val, state->error.location); + return; + } + + tevent_schedule_immediate(state->trans.im, state->ev, + tstream_smbXcli_np_readv_error_trigger, req); + + /* return the original error for writev */ + _tevent_req_error(cli_nps->trans.write_req, + state->error.val, state->error.location); +} + +static void tstream_smbXcli_np_readv_error_trigger(struct tevent_context *ctx, + struct tevent_immediate *im, + void *private_data) +{ + struct tevent_req *req = + talloc_get_type_abort(private_data, + struct tevent_req); + struct tstream_smbXcli_np_readv_state *state = + tevent_req_data(req, + struct tstream_smbXcli_np_readv_state); + + /* return the original error */ + _tevent_req_error(req, state->error.val, state->error.location); +} + +static int tstream_smbXcli_np_readv_recv(struct tevent_req *req, + int *perrno) +{ + struct tstream_smbXcli_np_readv_state *state = + tevent_req_data(req, struct tstream_smbXcli_np_readv_state); + int ret; + + ret = tsocket_simple_int_recv(req, perrno); + if (ret == 0) { + ret = state->ret; + } + + tevent_req_received(req); + return ret; +} + +struct tstream_smbXcli_np_disconnect_state { + struct tstream_context *stream; + struct tevent_req *subreq; +}; + +static void tstream_smbXcli_np_disconnect_done(struct tevent_req *subreq); +static void tstream_smbXcli_np_disconnect_cleanup(struct tevent_req *req, + enum tevent_req_state req_state); + +static struct tevent_req *tstream_smbXcli_np_disconnect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *stream) +{ + struct tstream_smbXcli_np *cli_nps = tstream_context_data(stream, + struct tstream_smbXcli_np); + struct tevent_req *req; + struct tstream_smbXcli_np_disconnect_state *state; + struct tevent_req *subreq; + + req = tevent_req_create(mem_ctx, &state, + struct tstream_smbXcli_np_disconnect_state); + if (req == NULL) { + return NULL; + } + + state->stream = stream; + + if (!smbXcli_conn_is_connected(cli_nps->conn)) { + tevent_req_error(req, ENOTCONN); + return tevent_req_post(req, ev); + } + + if (cli_nps->is_smb1) { + subreq = smb1cli_close_send(state, ev, cli_nps->conn, + cli_nps->timeout, + cli_nps->pid, + cli_nps->tcon, + cli_nps->session, + cli_nps->fnum, UINT32_MAX); + } else { + subreq = smb2cli_close_send(state, ev, cli_nps->conn, + cli_nps->timeout, + cli_nps->session, + cli_nps->tcon, + 0, /* flags */ + cli_nps->fid_persistent, + cli_nps->fid_volatile); + } + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, tstream_smbXcli_np_disconnect_done, req); + state->subreq = subreq; + + tevent_req_set_cleanup_fn(req, tstream_smbXcli_np_disconnect_cleanup); + + /* + * Make sure we don't send any requests anymore. + */ + cli_nps->conn = NULL; + + return req; +} + +static void tstream_smbXcli_np_disconnect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct tstream_smbXcli_np_disconnect_state *state = + tevent_req_data(req, struct tstream_smbXcli_np_disconnect_state); + struct tstream_smbXcli_np *cli_nps = + tstream_context_data(state->stream, struct tstream_smbXcli_np); + NTSTATUS status; + + state->subreq = NULL; + + if (cli_nps->is_smb1) { + status = smb1cli_close_recv(subreq); + } else { + status = smb2cli_close_recv(subreq); + } + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_error(req, EPIPE); + return; + } + + cli_nps->conn = NULL; + cli_nps->session = NULL; + cli_nps->tcon = NULL; + + tevent_req_done(req); +} + +static void tstream_smbXcli_np_disconnect_free(struct tevent_req *subreq); + +static void tstream_smbXcli_np_disconnect_cleanup(struct tevent_req *req, + enum tevent_req_state req_state) +{ + struct tstream_smbXcli_np_disconnect_state *state = + tevent_req_data(req, struct tstream_smbXcli_np_disconnect_state); + struct tstream_smbXcli_np *cli_nps = NULL; + + if (state->subreq == NULL) { + return; + } + + cli_nps = tstream_context_data(state->stream, struct tstream_smbXcli_np); + + if (cli_nps->tcon == NULL) { + return; + } + + /* + * We're no longer interested in the result + * any more, but need to make sure that the close + * request arrives at the server if the smb connection, + * session and tcon are still alive. + * + * We move the low level request to the tcon, + * which means that it stays as long as the tcon + * is available. + */ + talloc_steal(cli_nps->tcon, state->subreq); + tevent_req_set_callback(state->subreq, + tstream_smbXcli_np_disconnect_free, + NULL); + state->subreq = NULL; + + cli_nps->conn = NULL; + cli_nps->session = NULL; + cli_nps->tcon = NULL; +} + +static void tstream_smbXcli_np_disconnect_free(struct tevent_req *subreq) +{ + TALLOC_FREE(subreq); +} + +static int tstream_smbXcli_np_disconnect_recv(struct tevent_req *req, + int *perrno) +{ + int ret; + + ret = tsocket_simple_int_recv(req, perrno); + + tevent_req_received(req); + return ret; +} + +static const struct tstream_context_ops tstream_smbXcli_np_ops = { + .name = "smbXcli_np", + + .pending_bytes = tstream_smbXcli_np_pending_bytes, + + .readv_send = tstream_smbXcli_np_readv_send, + .readv_recv = tstream_smbXcli_np_readv_recv, + + .writev_send = tstream_smbXcli_np_writev_send, + .writev_recv = tstream_smbXcli_np_writev_recv, + + .disconnect_send = tstream_smbXcli_np_disconnect_send, + .disconnect_recv = tstream_smbXcli_np_disconnect_recv, +}; |