diff options
Diffstat (limited to '')
57 files changed, 21992 insertions, 0 deletions
diff --git a/libcli/smb/read_smb.c b/libcli/smb/read_smb.c new file mode 100644 index 0000000..a40f702 --- /dev/null +++ b/libcli/smb/read_smb.c @@ -0,0 +1,114 @@ +/* + Unix SMB/CIFS implementation. + Infrastructure for async SMB client requests + Copyright (C) Volker Lendecke 2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/network.h" +#include "lib/async_req/async_sock.h" +#include "read_smb.h" +#include "lib/util/tevent_unix.h" +#include "libcli/smb/smb_constants.h" + +/* + * Read an smb packet asynchronously, discard keepalives + */ + +struct read_smb_state { + struct tevent_context *ev; + int fd; + uint8_t *buf; +}; + +static ssize_t read_smb_more(uint8_t *buf, size_t buflen, void *private_data); +static void read_smb_done(struct tevent_req *subreq); + +struct tevent_req *read_smb_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd) +{ + struct tevent_req *result, *subreq; + struct read_smb_state *state; + + result = tevent_req_create(mem_ctx, &state, struct read_smb_state); + if (result == NULL) { + return NULL; + } + state->ev = ev; + state->fd = fd; + + subreq = read_packet_send(state, ev, fd, 4, read_smb_more, NULL); + if (subreq == NULL) { + goto fail; + } + tevent_req_set_callback(subreq, read_smb_done, result); + return result; + fail: + TALLOC_FREE(result); + return NULL; +} + +static ssize_t read_smb_more(uint8_t *buf, size_t buflen, void *private_data) +{ + if (buflen > 4) { + return 0; /* We've been here, we're done */ + } + return smb_len_tcp(buf); +} + +static void read_smb_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct read_smb_state *state = tevent_req_data( + req, struct read_smb_state); + ssize_t len; + int err; + + len = read_packet_recv(subreq, state, &state->buf, &err); + TALLOC_FREE(subreq); + if (len == -1) { + tevent_req_error(req, err); + return; + } + + if (CVAL(state->buf, 0) == NBSSkeepalive) { + subreq = read_packet_send(state, state->ev, state->fd, 4, + read_smb_more, NULL); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, read_smb_done, req); + return; + } + tevent_req_done(req); +} + +ssize_t read_smb_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + uint8_t **pbuf, int *perrno) +{ + struct read_smb_state *state = tevent_req_data( + req, struct read_smb_state); + + if (tevent_req_is_unix_error(req, perrno)) { + tevent_req_received(req); + return -1; + } + *pbuf = talloc_move(mem_ctx, &state->buf); + tevent_req_received(req); + return talloc_get_size(*pbuf); +} diff --git a/libcli/smb/read_smb.h b/libcli/smb/read_smb.h new file mode 100644 index 0000000..1df2dc2 --- /dev/null +++ b/libcli/smb/read_smb.h @@ -0,0 +1,33 @@ +/* + Unix SMB/CIFS implementation. + Infrastructure for async SMB client requests + Copyright (C) Volker Lendecke 2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __LIBSMB_READ_SMB_H +#define __LIBSMB_READ_SMB_H + +struct tevent_context; +struct tevent_req; + +struct tevent_req *read_smb_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd); + +ssize_t read_smb_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + uint8_t **pbuf, int *perrno); + +#endif diff --git a/libcli/smb/reparse_symlink.c b/libcli/smb/reparse_symlink.c new file mode 100644 index 0000000..04f26dc --- /dev/null +++ b/libcli/smb/reparse_symlink.c @@ -0,0 +1,212 @@ +/* + * Unix SMB/CIFS implementation. + * + * Implementation of + * http://msdn.microsoft.com/en-us/library/cc232006%28v=PROT.13%29.aspx + * + * Copyright (C) Volker Lendecke 2011 + * + * 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 "replace.h" +#include "reparse_symlink.h" +#include "lib/util/charset/charset.h" +#include "lib/util/byteorder.h" +#include "libcli/smb/smb_constants.h" +#include "libcli/smb/smb_util.h" +#include "lib/util/debug.h" + +bool symlink_reparse_buffer_marshall( + const char *substitute, const char *printname, uint32_t flags, + TALLOC_CTX *mem_ctx, uint8_t **pdst, size_t *pdstlen) +{ + uint8_t *dst = NULL; + size_t dst_len; + uint8_t *subst_utf16 = NULL; + uint8_t *print_utf16 = NULL; + size_t subst_len = 0; + size_t print_len = 0; + bool ret = false; + bool ok; + + if (substitute == NULL) { + return false; + } + if (printname == NULL) { + printname = substitute; + } + + ok = convert_string_talloc( + mem_ctx, + CH_UNIX, + CH_UTF16, + substitute, + strlen(substitute), + &subst_utf16, + &subst_len); + if (!ok) { + goto fail; + } + + ok = convert_string_talloc( + mem_ctx, + CH_UNIX, + CH_UTF16, + printname, + strlen(printname), + &print_utf16, + &print_len); + if (!ok) { + goto fail; + } + + dst_len = subst_len + 20; + if (dst_len < 20) { + goto fail; + } + dst_len += print_len; + if (dst_len < print_len) { + goto fail; + } + dst = talloc_array(mem_ctx, uint8_t, dst_len); + if (dst == NULL) { + goto fail; + } + + SIVAL(dst, 0, IO_REPARSE_TAG_SYMLINK); /* ReparseTag */ + SSVAL(dst, 4, 12 + subst_len + print_len); /* ReparseDataLength */ + SSVAL(dst, 6, 0); /* Reserved */ + SSVAL(dst, 8, 0); /* SubstituteNameOffset */ + SSVAL(dst, 10, subst_len); /* SubstituteNameLength */ + SSVAL(dst, 12, subst_len); /* PrintNameOffset */ + SSVAL(dst, 14, print_len); /* PrintNameLength */ + SIVAL(dst, 16, flags); /* Flags */ + + if ((subst_utf16 != NULL) && (subst_len != 0)) { + memcpy(dst + 20, subst_utf16, subst_len); + } + + if ((print_utf16 != NULL) && (print_len != 0)) { + memcpy(dst + 20 + subst_len, print_utf16, print_len); + } + + *pdst = dst; + *pdstlen = dst_len; + ret = true; + +fail: + TALLOC_FREE(subst_utf16); + TALLOC_FREE(print_utf16); + return ret; +} + +struct symlink_reparse_struct *symlink_reparse_buffer_parse( + TALLOC_CTX *mem_ctx, const uint8_t *src, size_t srclen) +{ + struct symlink_reparse_struct *result = NULL; + uint16_t reparse_data_length; + uint16_t substitute_name_offset, substitute_name_length; + uint16_t print_name_offset, print_name_length; + bool ok; + + if (srclen < 20) { + DBG_DEBUG("srclen = %zu, expected >= 20\n", srclen); + goto fail; + } + if (IVAL(src, 0) != IO_REPARSE_TAG_SYMLINK) { + DBG_DEBUG("Got ReparseTag %8.8x, expected %8.8x\n", + IVAL(src, 0), + IO_REPARSE_TAG_SYMLINK); + goto fail; + } + + reparse_data_length = SVAL(src, 4); + substitute_name_offset = SVAL(src, 8); + substitute_name_length = SVAL(src, 10); + print_name_offset = SVAL(src, 12); + print_name_length = SVAL(src, 14); + + if (reparse_data_length < 12) { + DBG_DEBUG("reparse_data_length = %"PRIu16", expected >= 12\n", + reparse_data_length); + goto fail; + } + if (smb_buffer_oob(srclen - 8, reparse_data_length, 0)) { + DBG_DEBUG("reparse_data_length (%"PRIu16") too large for " + "src_len (%zu)\n", + reparse_data_length, + srclen); + goto fail; + } + if (smb_buffer_oob(reparse_data_length - 12, substitute_name_offset, + substitute_name_length)) { + DBG_DEBUG("substitute_name (%"PRIu16"/%"PRIu16") does not fit " + "in reparse_data_length (%"PRIu16")\n", + substitute_name_offset, + substitute_name_length, + reparse_data_length - 12); + goto fail; + } + if (smb_buffer_oob(reparse_data_length - 12, print_name_offset, + print_name_length)) { + DBG_DEBUG("print_name (%"PRIu16"/%"PRIu16") does not fit in " + "reparse_data_length (%"PRIu16")\n", + print_name_offset, + print_name_length, + reparse_data_length - 12); + goto fail; + } + + result = talloc_zero(mem_ctx, struct symlink_reparse_struct); + if (result == NULL) { + DBG_DEBUG("talloc failed\n"); + goto fail; + } + + ok = convert_string_talloc( + result, + CH_UTF16, + CH_UNIX, + src + 20 + substitute_name_offset, + substitute_name_length, + &result->substitute_name, + NULL); + if (!ok) { + DBG_DEBUG("convert_string_talloc for substitute_name " + "failed\n"); + goto fail; + } + + ok = convert_string_talloc( + result, + CH_UTF16, + CH_UNIX, + src + 20 + print_name_offset, + print_name_length, + &result->print_name, + NULL); + if (!ok) { + DBG_DEBUG("convert_string_talloc for print_name failed\n"); + goto fail; + } + + result->unparsed_path_length = SVAL(src, 6); + result->flags = IVAL(src, 16); + + return result; +fail: + TALLOC_FREE(result); + return NULL; +} diff --git a/libcli/smb/reparse_symlink.h b/libcli/smb/reparse_symlink.h new file mode 100644 index 0000000..b561fa9 --- /dev/null +++ b/libcli/smb/reparse_symlink.h @@ -0,0 +1,42 @@ +/* + * Unix SMB/CIFS implementation. + * + * Implementation of + * http://msdn.microsoft.com/en-us/library/cc232006%28v=PROT.13%29.aspx + * + * Copyright (C) Volker Lendecke 2011 + * + * 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 __REPARSE_SYMLINK_H__ +#define __REPARSE_SYMLINK_H__ + +#include "replace.h" +#include <talloc.h> + +struct symlink_reparse_struct { + uint16_t unparsed_path_length; /* reserved for the reparse point */ + char *substitute_name; + char *print_name; + uint32_t flags; +}; + +bool symlink_reparse_buffer_marshall( + const char *substitute, const char *printname, uint32_t flags, + TALLOC_CTX *mem_ctx, uint8_t **pdst, size_t *pdstlen); +struct symlink_reparse_struct *symlink_reparse_buffer_parse( + TALLOC_CTX *mem_ctx, const uint8_t *src, size_t srclen); + +#endif diff --git a/libcli/smb/smb1cli_close.c b/libcli/smb/smb1cli_close.c new file mode 100644 index 0000000..89fbbba --- /dev/null +++ b/libcli/smb/smb1cli_close.c @@ -0,0 +1,188 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Gregor Beck 2013 + Copyright (C) Stefan Metzmacher 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 "system/network.h" +#include "lib/util/tevent_ntstatus.h" +#include "smb_common.h" +#include "smbXcli_base.h" + +struct smb1cli_close_state { + uint16_t vwv[3]; +}; + +static void smb1cli_close_done(struct tevent_req *subreq); + +/** + * Send an asynchronous SMB_COM_CLOSE request. + * <a href="http://msdn.microsoft.com/en-us/library/ee442151.aspx">MS-CIFS 2.2.4.5.1</a> + * @see smb1cli_close_recv(), smb1cli_close() + * + * @param[in] mem_ctx The memory context for the result. + * @param[in] ev The event context to work on. + * @param[in] conn The smb connection. + * @param[in] timeout_msec If positiv a timeout for the request. + * @param[in] pid The process identifier. + * @param[in] tcon The smb tree connect. + * @param[in] session The smb session. + * @param[in] fnum The file id of the file to be closed. + * @param[in] last_modified If not 0 or 0xFFFFFFFF request to set modification time to this number of seconds since January 1, 1970. + * + * @return a tevent_req or NULL. + */ +struct tevent_req *smb1cli_close_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + uint16_t fnum, + uint32_t last_modified) +{ + struct tevent_req *req, *subreq; + struct smb1cli_close_state *state; + + req = tevent_req_create(mem_ctx, &state, struct smb1cli_close_state); + if (req == NULL) { + return NULL; + } + + SSVAL(state->vwv+0, 0, fnum); + SIVALS(state->vwv+1, 0, last_modified); + + subreq = smb1cli_req_send(state, ev, conn, SMBclose, + 0, 0, /* *_flags */ + 0, 0, /* *_flags2 */ + timeout_msec, pid, tcon, session, + ARRAY_SIZE(state->vwv), state->vwv, + 0, NULL); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb1cli_close_done, req); + return req; +} + +static void smb1cli_close_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct smb1cli_close_state *state = tevent_req_data( + req, struct smb1cli_close_state); + NTSTATUS status; + static const struct smb1cli_req_expected_response expected[] = { + { + .status = NT_STATUS_OK, + .wct = 0x00 + }, + }; + + status = smb1cli_req_recv(subreq, state, + NULL, /* recv_iov */ + NULL, /* phdr */ + NULL, /* wct */ + NULL, /* vwv */ + NULL, /* pvwv_offset */ + NULL, /* num_bytes */ + NULL, /* bytes */ + NULL, /* pbytes_offset */ + NULL, /* inbuf */ + expected, ARRAY_SIZE(expected)); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); +} + +/** + * Receive the response to an asynchronous SMB_COM_CLOSE request. + * <a href="http://msdn.microsoft.com/en-us/library/ee441667.aspx">MS-CIFS 2.2.4.5.2</a> + * + * @param req A tevent_req created with smb1cli_close_send() + * + * @return NT_STATUS_OK on success + */ +NTSTATUS smb1cli_close_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +/** + * Send an synchronous SMB_COM_CLOSE request. + * <a href="http://msdn.microsoft.com/en-us/library/ee441493.aspx">MS-CIFS 2.2.4.5</a> + * @see smb1cli_close_send(), smb1cli_close_recv() + * + * @param[in] conn The smb connection. + * @param[in] timeout_msec If positiv a timeout for the request. + * @param[in] pid The process identifier. + * @param[in] tcon The smb tree connect. + * @param[in] session The smb session. + * @param[in] fnum The file id of the file to be closed. + * @param[in] last_modified If not 0 or 0xFFFFFFFF request to set modification time to this number of seconds since J + * + * @return NT_STATUS_OK on success. + */ +NTSTATUS smb1cli_close(struct smbXcli_conn *conn, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + uint16_t fnum, + uint32_t last_modified) +{ + NTSTATUS status = NT_STATUS_OK; + struct tevent_req *req; + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + + if (smbXcli_conn_has_async_calls(conn)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + req = smb1cli_close_send(frame, ev, conn, + timeout_msec, pid, tcon, session, + fnum, last_modified); + if (req == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto done; + } + + status = smb1cli_close_recv(req); +done: + talloc_free(frame); + return status; +} diff --git a/libcli/smb/smb1cli_create.c b/libcli/smb/smb1cli_create.c new file mode 100644 index 0000000..d9887a0 --- /dev/null +++ b/libcli/smb/smb1cli_create.c @@ -0,0 +1,296 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Gregor Beck 2013 + Copyright (C) Stefan Metzmacher 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 "system/network.h" +#include "lib/util/tevent_ntstatus.h" +#include "smb_common.h" +#include "smbXcli_base.h" + +struct smb1cli_ntcreatex_state { + uint16_t vwv[24]; + uint16_t fnum; +}; + +static void smb1cli_ntcreatex_done(struct tevent_req *subreq); + +/** + * Send an asynchronous SMB_COM_NT_CREATE_ANDX request. + * <a href="http://msdn.microsoft.com/en-us/library/ee442175.aspx">MS-CIFS 2.2.4.64.1</a> + * @see smb1cli_ntcreatex_recv(), smb1cli_ntcreatex() + * + * @param[in] mem_ctx The memory context for the result. + * @param[in] ev The event context to work on. + * @param[in] conn The smb connection. + * @param[in] timeout_msec If positiv a timeout for the request. + * @param[in] pid The process identifier + * @param[in] tcon The smb tree connect. + * @param[in] session The smb session. + * @param[in] fname The name of the file or directory to be opened or created. + * @param[in] CreatFlags + * @param[in] RootDirectoryFid The file id of an opened root directory fname is based on. 0 means root of the share. + * @param[in] DesiredAccess A field of flags that indicate standard, specific, and generic access rights to be requested. + * @param[in] AllocationSize Number of Bytes to allocate if the file is to be created or overwritten. + * @param[in] FileAttributes <a href="http://msdn.microsoft.com/en-us/library/ee878573.aspx">Extended file attributes</a> + * @param[in] ShareAccess A field that specifies how the file should be shared with other processes. + * @param[in] CreateDisposition A value that represents the action to take if the file already exists or if the file is a new file and does not already exist. + * @param[in] CreateOptions A field of flag options to use if creating a file or directory. + * @param[in] ImpersonationLevel + * @param[in] SecurityFlags + * + * @return a tevent_req or NULL + */ +struct tevent_req *smb1cli_ntcreatex_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + const char *fname, + uint32_t CreatFlags, + uint32_t RootDirectoryFid, + uint32_t DesiredAccess, + uint64_t AllocationSize, + uint32_t FileAttributes, + uint32_t ShareAccess, + uint32_t CreateDisposition, + uint32_t CreateOptions, + uint32_t ImpersonationLevel, + uint8_t SecurityFlags) +{ + struct tevent_req *req, *subreq; + struct smb1cli_ntcreatex_state *state; + uint8_t *bytes; + size_t converted_len; + + req = tevent_req_create(mem_ctx, &state, struct smb1cli_ntcreatex_state); + if (req == NULL) { + return NULL; + } + + SCVAL(state->vwv+0, 0, 0xFF); + SCVAL(state->vwv+0, 1, 0); + SSVAL(state->vwv+1, 0, 0); + SCVAL(state->vwv+2, 0, 0); + SIVAL(state->vwv+3, 1, CreatFlags); + SIVAL(state->vwv+5, 1, RootDirectoryFid); + SIVAL(state->vwv+7, 1, DesiredAccess); + SBVAL(state->vwv+9, 1, AllocationSize); + SIVAL(state->vwv+13, 1, FileAttributes); + SIVAL(state->vwv+15, 1, ShareAccess); + SIVAL(state->vwv+17, 1, CreateDisposition); + SIVAL(state->vwv+19, 1, CreateOptions); + SIVAL(state->vwv+21, 1, ImpersonationLevel); + SCVAL(state->vwv+23, 1, SecurityFlags); + + bytes = talloc_array(state, uint8_t, 0); + bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(conn), + fname, strlen(fname)+1, + &converted_len); + + /* sigh. this copes with broken netapp filer behaviour */ + bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(conn), "", 1, NULL); + + if (tevent_req_nomem(bytes, req)) { + return tevent_req_post(req, ev); + } + + SSVAL(state->vwv+2, 1, converted_len); + + subreq = smb1cli_req_send(state, ev, conn, SMBntcreateX, + 0, 0, /* *_flags */ + 0, 0, /* *_flags2 */ + timeout_msec, pid, tcon, session, + ARRAY_SIZE(state->vwv), state->vwv, + talloc_get_size(bytes), bytes); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb1cli_ntcreatex_done, req); + + return req; +} + +static void smb1cli_ntcreatex_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct smb1cli_ntcreatex_state *state = tevent_req_data( + req, struct smb1cli_ntcreatex_state); + struct iovec *recv_iov = NULL; + uint8_t wct; + uint16_t *vwv; + NTSTATUS status; + static const struct smb1cli_req_expected_response expected[] = { + { + .status = NT_STATUS_OK, + .wct = 0x22 + }, + { + /* + * This is the broken version see from + * [MS-SMB]: + * Windows-based SMB servers send 50 (0x32) words in the extended + * response although they set the WordCount field to 0x2A. + * + * And Samba does the same... + */ + .status = NT_STATUS_OK, + .wct = 0x2a + }, + { + .status = NT_STATUS_OK, + .wct = 0x32 + }, + }; + + status = smb1cli_req_recv(subreq, state, + &recv_iov, + NULL, /* phdr */ + &wct, + &vwv, + NULL, /* pvwv_offset */ + NULL, /* num_bytes */ + NULL, /* bytes */ + NULL, /* pbytes_offset */ + NULL, /* inbuf */ + expected, ARRAY_SIZE(expected)); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + state->fnum = SVAL(vwv+2, 1); + tevent_req_done(req); +} + +/** + * Receive the response to an asynchronous SMB_COM_NT_CREATE_ANDX request. + * <a href="http://msdn.microsoft.com/en-us/library/ee441612.aspx">MS-CIFS 2.2.4.64.2</a> + * + * @param[in] req A tevent request created with smb1cli_ntcreatex_send() + * @param[out] pfnum The file id of the opened file or directory. + * + * @return NT_STATUS_OK on succsess + */ +NTSTATUS smb1cli_ntcreatex_recv(struct tevent_req *req, uint16_t *pfnum) +{ + struct smb1cli_ntcreatex_state *state = tevent_req_data( + req, struct smb1cli_ntcreatex_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *pfnum = state->fnum; + tevent_req_received(req); + return NT_STATUS_OK; +} + + +/** + * Send a synchronous SMB_COM_NT_CREATE_ANDX request. + * <a href="http://msdn.microsoft.com/en-us/library/ee442091.aspx">MS-CIFS 2.2.4.64</a> + * @see smb1cli_ntcreatex_send() smb1cli_ntcreatex_recv() + * + * @param[in] conn The smb connection. + * @param[in] timeout_msec If positiv a timeout for the request. + * @param[in] pid The process identifier + * @param[in] tcon The smb tree connect. + * @param[in] session The smb session. + * @param[in] fname The name of the file or directory to be opened or created. + * @param[in] CreatFlags + * @param[in] RootDirectoryFid The file id of an opened root directory fname is based on. 0 means root of the share. + * @param[in] DesiredAccess A field of flags that indicate standard, specific, and generic access rights to be requested. + * @param[in] AllocationSize Number of Bytes to allocate if the file is to be created or overwritten. + * @param[in] FileAttributes <a href="http://msdn.microsoft.com/en-us/library/ee878573.aspx">Extended file attributes</a> + * @param[in] ShareAccess A field that specifies how the file should be shared with other processes. + * @param[in] CreateDisposition A value that represents the action to take if the file already exists or if the file is a new file and does not already exist. + * @param[in] CreateOptions A field of flag options to use if creating a file or directory. + * @param[in] ImpersonationLevel + * @param[in] SecurityFlags + * @param[out] pfnum The file id representing the file or directory created or opened. + * + * @return NT_STATUS_OK on succsess + */ +NTSTATUS smb1cli_ntcreatex(struct smbXcli_conn *conn, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + const char *fname, + uint32_t CreatFlags, + uint32_t RootDirectoryFid, + uint32_t DesiredAccess, + uint64_t AllocationSize, + uint32_t FileAttributes, + uint32_t ShareAccess, + uint32_t CreateDisposition, + uint32_t CreateOptions, + uint32_t ImpersonationLevel, + uint8_t SecurityFlags, + uint16_t *pfnum) +{ + TALLOC_CTX *frame = NULL; + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_OK; + + frame = talloc_stackframe(); + + if (smbXcli_conn_has_async_calls(conn)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER; + goto fail; + } + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + req = smb1cli_ntcreatex_send(frame, ev, conn, + timeout_msec, + pid, tcon, session, + fname, CreatFlags, RootDirectoryFid, + DesiredAccess, AllocationSize, + FileAttributes, ShareAccess, + CreateDisposition, CreateOptions, + ImpersonationLevel, SecurityFlags); + if (req == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + + status = smb1cli_ntcreatex_recv(req, pfnum); +fail: + TALLOC_FREE(frame); + return status; +} diff --git a/libcli/smb/smb1cli_echo.c b/libcli/smb/smb1cli_echo.c new file mode 100644 index 0000000..10dff2d --- /dev/null +++ b/libcli/smb/smb1cli_echo.c @@ -0,0 +1,169 @@ +/* + Unix SMB/CIFS implementation. + smb2 lib + Copyright (C) Stefan Metzmacher 2012 + + 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 "smb_common.h" +#include "smbXcli_base.h" + +struct smb1cli_echo_state { + uint16_t vwv[1]; + DATA_BLOB data; + uint16_t num_echos; +}; + +static void smb1cli_echo_done(struct tevent_req *subreq); + +struct tevent_req *smb1cli_echo_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + uint16_t num_echos, + DATA_BLOB data) +{ + struct tevent_req *req, *subreq; + struct smb1cli_echo_state *state; + + req = tevent_req_create(mem_ctx, &state, struct smb1cli_echo_state); + if (req == NULL) { + return NULL; + } + SSVAL(state->vwv, 0, num_echos); + state->data = data; + state->num_echos = num_echos; + + subreq = smb1cli_req_send(state, ev, conn, SMBecho, + 0, 0, /* *_flags */ + 0, 0, /* *_flags2 */ + timeout_msec, + 0, /* pid */ + NULL, /* tcon */ + NULL, /* session */ + ARRAY_SIZE(state->vwv), state->vwv, + data.length, data.data); + if (subreq == NULL) { + goto fail; + } + tevent_req_set_callback(subreq, smb1cli_echo_done, req); + return req; + fail: + TALLOC_FREE(req); + return NULL; +} + +static void smb1cli_echo_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct smb1cli_echo_state *state = tevent_req_data( + req, struct smb1cli_echo_state); + NTSTATUS status; + uint32_t num_bytes; + uint8_t *bytes; + struct iovec *recv_iov; + struct smb1cli_req_expected_response expected[] = { + { + .status = NT_STATUS_OK, + .wct = 1, + }, + }; + + status = smb1cli_req_recv(subreq, state, + &recv_iov, + NULL, /* phdr */ + NULL, /* pwct */ + NULL, /* pvwv */ + NULL, /* pvwv_offset */ + &num_bytes, + &bytes, + NULL, /* pbytes_offset */ + NULL, /* pinbuf */ + expected, ARRAY_SIZE(expected)); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return; + } + + if (num_bytes != state->data.length) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + if (memcmp(bytes, state->data.data, num_bytes) != 0) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + /* TODO: do we want to verify the sequence number? */ + + state->num_echos -=1; + if (state->num_echos == 0) { + tevent_req_done(req); + return; + } + + if (!smbXcli_req_set_pending(subreq)) { + tevent_req_nterror(req, NT_STATUS_NO_MEMORY); + return; + } +} + +/** + * Get the result out from an echo request + * @param[in] req The async_req from smb1cli_echo_send + * @retval Did the server reply correctly? + */ + +NTSTATUS smb1cli_echo_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +NTSTATUS smb1cli_echo(struct smbXcli_conn *conn, uint32_t timeout_msec, + uint16_t num_echos, DATA_BLOB data) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + if (smbXcli_conn_has_async_calls(conn)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER; + goto fail; + } + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = smb1cli_echo_send(frame, ev, conn, timeout_msec, num_echos, data); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = smb1cli_echo_recv(req); + fail: + TALLOC_FREE(frame); + return status; +} diff --git a/libcli/smb/smb1cli_read.c b/libcli/smb/smb1cli_read.c new file mode 100644 index 0000000..d7a7f43 --- /dev/null +++ b/libcli/smb/smb1cli_read.c @@ -0,0 +1,241 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Gregor Beck 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 "system/network.h" +#include "lib/util/tevent_ntstatus.h" +#include "smb_common.h" +#include "smbXcli_base.h" + +struct smb1cli_readx_state { + uint32_t size; + uint16_t vwv[12]; + uint32_t received; + uint8_t *buf; + bool out_valid; +}; + +static void smb1cli_readx_done(struct tevent_req *subreq); + +/** + * Send an asynchrounus SMB_COM_READ_ANDX request. + * <a href="http://msdn.microsoft.com/en-us/library/ee441839.aspx">MS-CIFS 2.2.4.42.1</a>, + * <a href="http://msdn.microsoft.com/en-us/library/ff470250.aspx">MS-SMB 2.2.4.2.1</a> + * @see smb1cli_readx_recv() + * @todo fix API (min/max size, timeout) + * + * @param[in] mem_ctx The memory context for the result. + * @param[in] ev The event context to work on. + * @param[in] conn The smb connection. + * @param[in] timeout_msec If positiv a timeout for the request. + * @param[in] pid The process identifier + * @param[in] tcon The smb tree connect. + * @param[in] session The smb session. + * @param[in] fnum The file id of the file the data should be read from. + * @param[in] offset The offset in bytes from the begin of file where to start reading. + * @param[in] size The number of bytes to read. + * + * @return a tevent_req or NULL + */ +struct tevent_req *smb1cli_readx_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + uint16_t fnum, + uint64_t offset, + uint32_t size) +{ + NTSTATUS status; + struct tevent_req *req, *subreq; + struct smb1cli_readx_state *state; + uint8_t wct = 10; + + req = tevent_req_create(mem_ctx, &state, struct smb1cli_readx_state); + if (req == NULL) { + return NULL; + } + state->size = size; + + SCVAL(state->vwv + 0, 0, 0xFF); + SCVAL(state->vwv + 0, 1, 0); + SSVAL(state->vwv + 1, 0, 0); + SSVAL(state->vwv + 2, 0, fnum); + SIVAL(state->vwv + 3, 0, offset); + SSVAL(state->vwv + 5, 0, size); + SSVAL(state->vwv + 6, 0, size); + SSVAL(state->vwv + 7, 0, (size >> 16)); + SSVAL(state->vwv + 8, 0, 0); + SSVAL(state->vwv + 9, 0, 0); + + if (smb1cli_conn_capabilities(conn) & CAP_LARGE_FILES) { + SIVAL(state->vwv + 10, 0, + (((uint64_t)offset)>>32) & 0xffffffff); + wct = 12; + } else { + if ((((uint64_t)offset) & 0xffffffff00000000LL) != 0) { + DEBUG(10, ("smb1cli_readx_send got large offset where " + "the server does not support it\n")); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + } + + subreq = smb1cli_req_create(state, ev, conn, SMBreadX, + 0, 0, /* *_flags */ + 0, 0, /* *_flags2 */ + timeout_msec, pid, tcon, session, + wct, state->vwv, + 0, NULL); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb1cli_readx_done, req); + + status = smb1cli_req_chain_submit(&subreq, 1); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void smb1cli_readx_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct smb1cli_readx_state *state = tevent_req_data( + req, struct smb1cli_readx_state); + struct iovec *recv_iov = NULL; + uint8_t wct; + uint16_t *vwv; + uint32_t num_bytes; + uint8_t *bytes; + uint16_t data_offset; + uint32_t bytes_offset; + NTSTATUS status; + static const struct smb1cli_req_expected_response expected[] = { + { + .status = NT_STATUS_OK, + .wct = 0x0C + }, + { + .status = STATUS_BUFFER_OVERFLOW, + .wct = 0x0C + }, + }; + + status = smb1cli_req_recv(subreq, state, + &recv_iov, + NULL, /* phdr */ + &wct, + &vwv, + NULL, /* pvwv_offset */ + &num_bytes, + &bytes, + &bytes_offset, + NULL, /* inbuf */ + expected, ARRAY_SIZE(expected)); + TALLOC_FREE(subreq); + if (NT_STATUS_EQUAL(status, STATUS_BUFFER_OVERFLOW)) { + /* no error */ + } else { + if (tevent_req_nterror(req, status)) { + return; + } + } + + /* size is the number of bytes the server returned. + * Might be zero. */ + state->received = SVAL(vwv + 5, 0); + state->received |= (((unsigned int)SVAL(vwv + 7, 0)) << 16); + + if (state->received > state->size) { + DEBUG(5,("server returned more than we wanted!\n")); + tevent_req_nterror(req, NT_STATUS_UNEXPECTED_IO_ERROR); + return; + } + + /* + * bcc field must be valid for small reads, for large reads the 16-bit + * bcc field can't be correct. + */ + if ((state->received < 0xffff) && (state->received > num_bytes)) { + DEBUG(5, ("server announced more bytes than sent\n")); + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + data_offset = SVAL(vwv+6, 0); + if (data_offset < bytes_offset) { + DEBUG(5, ("server returned invalid read&x data offset\n")); + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + if (smb_buffer_oob(num_bytes, data_offset - bytes_offset, state->received)) { + DEBUG(5, ("server returned invalid read&x data offset\n")); + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + state->buf = bytes + (data_offset - bytes_offset); + + state->out_valid = true; + + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); +} + +/** + * Receive the response to an asynchronous SMB_COM_READ_ANDX request. + * <a href="http://msdn.microsoft.com/en-us/library/ee441872.aspx">MS-CIFS 2.2.4.42.2</a>, + * <a href="http://msdn.microsoft.com/en-us/library/ff470017.aspx">MS-SMB 2.2.4.2.2</a> + * + * @warning rcvbuf is talloced from the request, so better make sure that you + * copy it away before you talloc_free(req). rcvbuf is NOT a talloc_ctx of its + * own, so do not talloc_move it! + * + * @param[in] req A tevent request created with smb1cli_readx_send() + * @param[out] received The number of bytes received. + * @param[out] rcvbuf Pointer to the bytes received. + * + * @return NT_STATUS_OK or STATUS_BUFFER_OVERFLOW on succsess. + */ +NTSTATUS smb1cli_readx_recv(struct tevent_req *req, + uint32_t *received, + uint8_t **rcvbuf) +{ + struct smb1cli_readx_state *state = tevent_req_data( + req, struct smb1cli_readx_state); + NTSTATUS status = NT_STATUS_OK; + + if (tevent_req_is_nterror(req, &status) && !state->out_valid) { + *received = 0; + *rcvbuf = NULL; + return status; + } + *received = state->received; + *rcvbuf = state->buf; + return status; +} diff --git a/libcli/smb/smb1cli_session.c b/libcli/smb/smb1cli_session.c new file mode 100644 index 0000000..a8ad9b8 --- /dev/null +++ b/libcli/smb/smb1cli_session.c @@ -0,0 +1,821 @@ +/* + Unix SMB/CIFS implementation. + client connect/disconnect routines + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Andrew Bartlett 2001-2003 + Copyright (C) Volker Lendecke 2011 + Copyright (C) Jeremy Allison 2011 + Copyright (C) Stefan Metzmacher 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/network.h" +#include "../lib/util/tevent_ntstatus.h" +#include "../libcli/smb/smb_common.h" +#include "../libcli/smb/smbXcli_base.h" + + +struct smb1cli_session_setup_lm21_state { + struct smbXcli_session *session; + uint16_t vwv[10]; + struct iovec *recv_iov; + uint16_t out_session_id; + uint16_t out_action; + char *out_native_os; + char *out_native_lm; +}; + +static void smb1cli_session_setup_lm21_done(struct tevent_req *subreq); + +struct tevent_req *smb1cli_session_setup_lm21_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_session *session, + uint16_t in_buf_size, + uint16_t in_mpx_max, + uint16_t in_vc_num, + uint32_t in_sess_key, + const char *in_user, + const char *in_domain, + const DATA_BLOB in_apassword, + const char *in_native_os, + const char *in_native_lm) +{ + struct tevent_req *req = NULL; + struct smb1cli_session_setup_lm21_state *state = NULL; + struct tevent_req *subreq = NULL; + uint16_t *vwv = NULL; + uint8_t *bytes = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct smb1cli_session_setup_lm21_state); + if (req == NULL) { + return NULL; + } + state->session = session; + vwv = state->vwv; + + if (in_user == NULL) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + + if (in_domain == NULL) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + + if (in_apassword.length > UINT16_MAX) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + + if (in_native_os == NULL && in_native_lm != NULL) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + + SCVAL(vwv+0, 0, 0xff); + SCVAL(vwv+0, 1, 0); + SSVAL(vwv+1, 0, 0); + SSVAL(vwv+2, 0, in_buf_size); + SSVAL(vwv+3, 0, in_mpx_max); + SSVAL(vwv+4, 0, in_vc_num); + SIVAL(vwv+5, 0, in_sess_key); + SSVAL(vwv+7, 0, in_apassword.length); + SSVAL(vwv+8, 0, 0); /* reserved */ + SSVAL(vwv+9, 0, 0); /* reserved */ + + bytes = talloc_array(state, uint8_t, + in_apassword.length); + if (tevent_req_nomem(bytes, req)) { + return tevent_req_post(req, ev); + } + if (in_apassword.length != 0) { + memcpy(bytes, + in_apassword.data, + in_apassword.length); + } + + bytes = smb_bytes_push_str(bytes, + smbXcli_conn_use_unicode(conn), + in_user, strlen(in_user)+1, + NULL); + bytes = smb_bytes_push_str(bytes, + smbXcli_conn_use_unicode(conn), + in_domain, strlen(in_domain)+1, + NULL); + if (in_native_os != NULL) { + bytes = smb_bytes_push_str(bytes, + smbXcli_conn_use_unicode(conn), + in_native_os, strlen(in_native_os)+1, + NULL); + } + if (in_native_lm != NULL) { + bytes = smb_bytes_push_str(bytes, + smbXcli_conn_use_unicode(conn), + in_native_lm, strlen(in_native_lm)+1, + NULL); + } + if (tevent_req_nomem(bytes, req)) { + return tevent_req_post(req, ev); + } + + subreq = smb1cli_req_send(state, ev, conn, + SMBsesssetupX, + 0, /* additional_flags */ + 0, /* clear_flags */ + 0, /* additional_flags2 */ + 0, /* clear_flags2 */ + timeout_msec, + pid, + NULL, /* tcon */ + session, + 10, /* wct */ + vwv, + talloc_get_size(bytes), + bytes); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb1cli_session_setup_lm21_done, req); + + return req; +} + +static void smb1cli_session_setup_lm21_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct smb1cli_session_setup_lm21_state *state = + tevent_req_data(req, + struct smb1cli_session_setup_lm21_state); + NTSTATUS status; + uint8_t *inhdr = NULL; + uint8_t wct; + uint16_t *vwv = NULL; + uint32_t num_bytes; + uint8_t *bytes = NULL; + const uint8_t *p = NULL; + size_t ret = 0; + uint16_t flags2; + bool use_unicode = false; + struct smb1cli_req_expected_response expected[] = { + { + .status = NT_STATUS_OK, + .wct = 3, + }, + }; + + status = smb1cli_req_recv(subreq, state, + &state->recv_iov, + &inhdr, + &wct, + &vwv, + NULL, /* pvwv_offset */ + &num_bytes, + &bytes, + NULL, /* pbytes_offset */ + NULL, /* pinbuf */ + expected, ARRAY_SIZE(expected)); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + flags2 = SVAL(inhdr, HDR_FLG2); + if (flags2 & FLAGS2_UNICODE_STRINGS) { + use_unicode = true; + } + + state->out_session_id = SVAL(inhdr, HDR_UID); + state->out_action = SVAL(vwv+2, 0); + + p = bytes; + + status = smb_bytes_pull_str(state, &state->out_native_os, + use_unicode, bytes, num_bytes, + p, &ret); + if (tevent_req_nterror(req, status)) { + return; + } + p += ret; + + status = smb_bytes_pull_str(state, &state->out_native_lm, + use_unicode, bytes, num_bytes, + p, &ret); + if (tevent_req_nterror(req, status)) { + return; + } + + smb1cli_session_set_id(state->session, state->out_session_id); + smb1cli_session_set_action(state->session, state->out_action); + + tevent_req_done(req); +} + +NTSTATUS smb1cli_session_setup_lm21_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + char **out_native_os, + char **out_native_lm) +{ + struct smb1cli_session_setup_lm21_state *state = + tevent_req_data(req, + struct smb1cli_session_setup_lm21_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + if (out_native_os != NULL) { + *out_native_os = talloc_move(mem_ctx, &state->out_native_os); + } + + if (out_native_lm != NULL) { + *out_native_lm = talloc_move(mem_ctx, &state->out_native_lm); + } + + tevent_req_received(req); + return NT_STATUS_OK; +} + +struct smb1cli_session_setup_nt1_state { + struct smbXcli_session *session; + uint16_t vwv[13]; + struct iovec *recv_iov; + uint8_t *inbuf; + uint16_t out_session_id; + uint16_t out_action; + char *out_native_os; + char *out_native_lm; + char *out_primary_domain; +}; + +static void smb1cli_session_setup_nt1_done(struct tevent_req *subreq); + +struct tevent_req *smb1cli_session_setup_nt1_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_session *session, + uint16_t in_buf_size, + uint16_t in_mpx_max, + uint16_t in_vc_num, + uint32_t in_sess_key, + const char *in_user, + const char *in_domain, + const DATA_BLOB in_apassword, + const DATA_BLOB in_upassword, + uint32_t in_capabilities, + const char *in_native_os, + const char *in_native_lm) +{ + struct tevent_req *req = NULL; + struct smb1cli_session_setup_nt1_state *state = NULL; + struct tevent_req *subreq = NULL; + uint16_t *vwv = NULL; + uint8_t *bytes = NULL; + size_t align_upassword = 0; + size_t apassword_ofs = 0; + size_t upassword_ofs = 0; + + req = tevent_req_create(mem_ctx, &state, + struct smb1cli_session_setup_nt1_state); + if (req == NULL) { + return NULL; + } + state->session = session; + vwv = state->vwv; + + if (in_user == NULL) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + + if (in_domain == NULL) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + + if (in_apassword.length > UINT16_MAX) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + + if (in_upassword.length > UINT16_MAX) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + + if (in_native_os == NULL && in_native_lm != NULL) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + + SCVAL(vwv+0, 0, 0xff); + SCVAL(vwv+0, 1, 0); + SSVAL(vwv+1, 0, 0); + SSVAL(vwv+2, 0, in_buf_size); + SSVAL(vwv+3, 0, in_mpx_max); + SSVAL(vwv+4, 0, in_vc_num); + SIVAL(vwv+5, 0, in_sess_key); + SSVAL(vwv+7, 0, in_apassword.length); + SSVAL(vwv+8, 0, in_upassword.length); + SSVAL(vwv+9, 0, 0); /* reserved */ + SSVAL(vwv+10, 0, 0); /* reserved */ + SIVAL(vwv+11, 0, in_capabilities); + + if (in_apassword.length == 0 && in_upassword.length > 0) { + /* + * This is plaintext auth with a unicode password, + * we need to align the buffer. + * + * This is what smbclient and Windows XP send as + * a client. And what smbd expects. + * + * But it doesn't follow [MS-CIFS] (v20160714) + * 2.2.4.53.1 SMB_COM_SESSION_SETUP_ANDX Request: + * + * ... + * + * If SMB_FLAGS2_UNICODE is set (1), the value of OEMPasswordLen + * MUST be 0x0000 and the password MUST be encoded using + * UTF-16LE Unicode. Padding MUST NOT be added to + * align this plaintext Unicode string to a word boundary. + * + * ... + */ + uint16_t security_mode = smb1cli_conn_server_security_mode(conn); + + if (!(security_mode & NEGOTIATE_SECURITY_CHALLENGE_RESPONSE)) { + align_upassword = 1; + } + } + + bytes = talloc_array(state, uint8_t, + in_apassword.length + + align_upassword + + in_upassword.length); + if (tevent_req_nomem(bytes, req)) { + return tevent_req_post(req, ev); + } + if (in_apassword.length != 0) { + memcpy(bytes + apassword_ofs, + in_apassword.data, + in_apassword.length); + upassword_ofs += in_apassword.length; + } + if (align_upassword != 0) { + memset(bytes + upassword_ofs, 0, align_upassword); + upassword_ofs += align_upassword; + } + if (in_upassword.length != 0) { + memcpy(bytes + upassword_ofs, + in_upassword.data, + in_upassword.length); + } + + bytes = smb_bytes_push_str(bytes, + smbXcli_conn_use_unicode(conn), + in_user, strlen(in_user)+1, + NULL); + bytes = smb_bytes_push_str(bytes, + smbXcli_conn_use_unicode(conn), + in_domain, strlen(in_domain)+1, + NULL); + if (in_native_os != NULL) { + bytes = smb_bytes_push_str(bytes, + smbXcli_conn_use_unicode(conn), + in_native_os, strlen(in_native_os)+1, + NULL); + } + if (in_native_lm != NULL) { + bytes = smb_bytes_push_str(bytes, + smbXcli_conn_use_unicode(conn), + in_native_lm, strlen(in_native_lm)+1, + NULL); + } + if (tevent_req_nomem(bytes, req)) { + return tevent_req_post(req, ev); + } + + subreq = smb1cli_req_send(state, ev, conn, + SMBsesssetupX, + 0, /* additional_flags */ + 0, /* clear_flags */ + 0, /* additional_flags2 */ + 0, /* clear_flags2 */ + timeout_msec, + pid, + NULL, /* tcon */ + session, + 13, /* wct */ + vwv, + talloc_get_size(bytes), + bytes); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb1cli_session_setup_nt1_done, req); + + return req; +} + +static void smb1cli_session_setup_nt1_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct smb1cli_session_setup_nt1_state *state = + tevent_req_data(req, + struct smb1cli_session_setup_nt1_state); + NTSTATUS status; + uint8_t *inhdr = NULL; + uint8_t wct; + uint16_t *vwv = NULL; + uint32_t num_bytes; + uint8_t *bytes = NULL; + const uint8_t *p = NULL; + size_t ret = 0; + uint16_t flags2; + bool use_unicode = false; + struct smb1cli_req_expected_response expected[] = { + { + .status = NT_STATUS_OK, + .wct = 3, + }, + }; + + status = smb1cli_req_recv(subreq, state, + &state->recv_iov, + &inhdr, + &wct, + &vwv, + NULL, /* pvwv_offset */ + &num_bytes, + &bytes, + NULL, /* pbytes_offset */ + &state->inbuf, + expected, ARRAY_SIZE(expected)); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + flags2 = SVAL(inhdr, HDR_FLG2); + if (flags2 & FLAGS2_UNICODE_STRINGS) { + use_unicode = true; + } + + state->out_session_id = SVAL(inhdr, HDR_UID); + state->out_action = SVAL(vwv+2, 0); + + p = bytes; + + status = smb_bytes_pull_str(state, &state->out_native_os, + use_unicode, bytes, num_bytes, + p, &ret); + if (tevent_req_nterror(req, status)) { + return; + } + p += ret; + + status = smb_bytes_pull_str(state, &state->out_native_lm, + use_unicode, bytes, num_bytes, + p, &ret); + if (tevent_req_nterror(req, status)) { + return; + } + p += ret; + + status = smb_bytes_pull_str(state, &state->out_primary_domain, + use_unicode, bytes, num_bytes, + p, &ret); + if (tevent_req_nterror(req, status)) { + return; + } + + smb1cli_session_set_id(state->session, state->out_session_id); + smb1cli_session_set_action(state->session, state->out_action); + + tevent_req_done(req); +} + +NTSTATUS smb1cli_session_setup_nt1_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct iovec **precv_iov, + const uint8_t **precv_inbuf, + char **out_native_os, + char **out_native_lm, + char **out_primary_domain) +{ + struct smb1cli_session_setup_nt1_state *state = + tevent_req_data(req, + struct smb1cli_session_setup_nt1_state); + NTSTATUS status; + struct iovec *recv_iov = NULL; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + recv_iov = talloc_move(mem_ctx, &state->recv_iov); + if (precv_iov != NULL) { + *precv_iov = recv_iov; + } + if (precv_inbuf != NULL) { + *precv_inbuf = state->inbuf; + } + + if (out_native_os != NULL) { + *out_native_os = talloc_move(mem_ctx, &state->out_native_os); + } + + if (out_native_lm != NULL) { + *out_native_lm = talloc_move(mem_ctx, &state->out_native_lm); + } + + if (out_primary_domain != NULL) { + *out_primary_domain = talloc_move(mem_ctx, + &state->out_primary_domain); + } + + tevent_req_received(req); + return NT_STATUS_OK; +} + +struct smb1cli_session_setup_ext_state { + struct smbXcli_session *session; + uint16_t vwv[12]; + struct iovec *recv_iov; + uint8_t *inbuf; + NTSTATUS status; + uint16_t out_session_id; + uint16_t out_action; + DATA_BLOB out_security_blob; + char *out_native_os; + char *out_native_lm; +}; + +static void smb1cli_session_setup_ext_done(struct tevent_req *subreq); + +struct tevent_req *smb1cli_session_setup_ext_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_session *session, + uint16_t in_buf_size, + uint16_t in_mpx_max, + uint16_t in_vc_num, + uint32_t in_sess_key, + const DATA_BLOB in_security_blob, + uint32_t in_capabilities, + const char *in_native_os, + const char *in_native_lm) +{ + struct tevent_req *req = NULL; + struct smb1cli_session_setup_ext_state *state = NULL; + struct tevent_req *subreq = NULL; + uint16_t *vwv = NULL; + uint8_t *bytes = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct smb1cli_session_setup_ext_state); + if (req == NULL) { + return NULL; + } + state->session = session; + vwv = state->vwv; + + if (in_security_blob.length > UINT16_MAX) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + + if (in_native_os == NULL && in_native_lm != NULL) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + + SCVAL(vwv+0, 0, 0xff); + SCVAL(vwv+0, 1, 0); + SSVAL(vwv+1, 0, 0); + SSVAL(vwv+2, 0, in_buf_size); + SSVAL(vwv+3, 0, in_mpx_max); + SSVAL(vwv+4, 0, in_vc_num); + SIVAL(vwv+5, 0, in_sess_key); + SSVAL(vwv+7, 0, in_security_blob.length); + SSVAL(vwv+8, 0, 0); /* reserved */ + SSVAL(vwv+9, 0, 0); /* reserved */ + SIVAL(vwv+10, 0, in_capabilities); + + bytes = talloc_array(state, uint8_t, + in_security_blob.length); + if (tevent_req_nomem(bytes, req)) { + return tevent_req_post(req, ev); + } + if (in_security_blob.length != 0) { + memcpy(bytes, + in_security_blob.data, + in_security_blob.length); + } + + if (in_native_os != NULL) { + bytes = smb_bytes_push_str(bytes, + smbXcli_conn_use_unicode(conn), + in_native_os, strlen(in_native_os)+1, + NULL); + } + if (in_native_lm != NULL) { + bytes = smb_bytes_push_str(bytes, + smbXcli_conn_use_unicode(conn), + in_native_lm, strlen(in_native_lm)+1, + NULL); + } + if (tevent_req_nomem(bytes, req)) { + return tevent_req_post(req, ev); + } + + subreq = smb1cli_req_send(state, ev, conn, + SMBsesssetupX, + 0, /* additional_flags */ + 0, /* clear_flags */ + 0, /* additional_flags2 */ + 0, /* clear_flags2 */ + timeout_msec, + pid, + NULL, /* tcon */ + session, + 12, /* wct */ + vwv, + talloc_get_size(bytes), + bytes); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb1cli_session_setup_ext_done, req); + + return req; +} + +static void smb1cli_session_setup_ext_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct smb1cli_session_setup_ext_state *state = + tevent_req_data(req, + struct smb1cli_session_setup_ext_state); + NTSTATUS status; + uint8_t *inhdr = NULL; + uint8_t wct; + uint16_t *vwv = NULL; + uint32_t num_bytes; + uint8_t *bytes = NULL; + const uint8_t *p = NULL; + size_t ret = 0; + uint16_t flags2; + uint16_t out_security_blob_length = 0; + bool use_unicode = false; + struct smb1cli_req_expected_response expected[] = { + { + .status = NT_STATUS_OK, + .wct = 4, + }, + { + .status = NT_STATUS_MORE_PROCESSING_REQUIRED, + .wct = 4, + }, + }; + + status = smb1cli_req_recv(subreq, state, + &state->recv_iov, + &inhdr, + &wct, + &vwv, + NULL, /* pvwv_offset */ + &num_bytes, + &bytes, + NULL, /* pbytes_offset */ + &state->inbuf, + expected, ARRAY_SIZE(expected)); + TALLOC_FREE(subreq); + state->status = status; + if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + status = NT_STATUS_OK; + } + if (tevent_req_nterror(req, status)) { + return; + } + + flags2 = SVAL(inhdr, HDR_FLG2); + if (flags2 & FLAGS2_UNICODE_STRINGS) { + use_unicode = true; + } + + state->out_session_id = SVAL(inhdr, HDR_UID); + state->out_action = SVAL(vwv+2, 0); + out_security_blob_length = SVAL(vwv+3, 0); + + if (out_security_blob_length > num_bytes) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + p = bytes; + + /* + * Note: this points into state->recv_iov! + */ + state->out_security_blob = data_blob_const(p, out_security_blob_length); + p += out_security_blob_length; + + status = smb_bytes_pull_str(state, &state->out_native_os, + use_unicode, bytes, num_bytes, + p, &ret); + if (tevent_req_nterror(req, status)) { + return; + } + p += ret; + + status = smb_bytes_pull_str(state, &state->out_native_lm, + use_unicode, bytes, num_bytes, + p, &ret); + if (tevent_req_nterror(req, status)) { + return; + } + /* p += ret; */ + + smb1cli_session_set_id(state->session, state->out_session_id); + smb1cli_session_set_action(state->session, state->out_action); + + tevent_req_done(req); +} + +NTSTATUS smb1cli_session_setup_ext_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct iovec **precv_iov, + const uint8_t **precv_inbuf, + DATA_BLOB *out_security_blob, + char **out_native_os, + char **out_native_lm) +{ + struct smb1cli_session_setup_ext_state *state = + tevent_req_data(req, + struct smb1cli_session_setup_ext_state); + NTSTATUS status; + struct iovec *recv_iov = NULL; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + recv_iov = talloc_move(mem_ctx, &state->recv_iov); + if (precv_iov != NULL) { + *precv_iov = recv_iov; + } + if (precv_inbuf != NULL) { + *precv_inbuf = state->inbuf; + } + + *out_security_blob = state->out_security_blob; + + if (out_native_os != NULL) { + *out_native_os = talloc_move(mem_ctx, &state->out_native_os); + } + + if (out_native_lm != NULL) { + *out_native_lm = talloc_move(mem_ctx, &state->out_native_lm); + } + + /* + * Return the status from the server: + * NT_STATUS_MORE_PROCESSING_REQUIRED or + * NT_STATUS_OK. + */ + status = state->status; + tevent_req_received(req); + return status; +} diff --git a/libcli/smb/smb1cli_trans.c b/libcli/smb/smb1cli_trans.c new file mode 100644 index 0000000..99021ce --- /dev/null +++ b/libcli/smb/smb1cli_trans.c @@ -0,0 +1,908 @@ +/* + Unix SMB/CIFS implementation. + client transaction calls + Copyright (C) Andrew Tridgell 1994-1998 + + 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 "../libcli/smb/smb_common.h" +#include "../libcli/smb/smbXcli_base.h" + +struct trans_recvblob { + uint8_t *data; + uint32_t max, total, received; +}; + +struct smb1cli_trans_state { + struct smbXcli_conn *conn; + struct tevent_context *ev; + uint8_t cmd; + uint8_t additional_flags; + uint8_t clear_flags; + uint16_t additional_flags2; + uint16_t clear_flags2; + uint32_t timeout_msec; + uint16_t mid; + uint32_t pid; + struct smbXcli_tcon *tcon; + struct smbXcli_session *session; + const char *pipe_name; + uint8_t *pipe_name_conv; + size_t pipe_name_conv_len; + uint16_t fid; + uint16_t function; + int flags; + uint16_t *setup; + uint8_t num_setup, max_setup; + uint8_t *param; + uint32_t num_param, param_sent; + uint8_t *data; + uint32_t num_data, data_sent; + + uint8_t num_rsetup; + uint16_t *rsetup; + struct trans_recvblob rparam; + struct trans_recvblob rdata; + uint16_t recv_flags2; + + struct iovec iov[6]; + uint8_t pad[4]; + uint8_t zero_pad[4]; + uint16_t vwv[32]; + + NTSTATUS status; + + struct tevent_req *primary_subreq; +}; + +static void smb1cli_trans_cleanup_primary(struct smb1cli_trans_state *state) +{ + if (state->primary_subreq) { + smb1cli_req_set_mid(state->primary_subreq, 0); + smbXcli_req_unset_pending(state->primary_subreq); + TALLOC_FREE(state->primary_subreq); + } +} + +static int smb1cli_trans_state_destructor(struct smb1cli_trans_state *state) +{ + smb1cli_trans_cleanup_primary(state); + return 0; +} + +static NTSTATUS smb1cli_pull_trans(uint8_t *inhdr, + uint8_t wct, + uint16_t *vwv, + uint32_t vwv_ofs, + uint32_t num_bytes, + uint8_t *bytes, + uint32_t bytes_ofs, + uint8_t smb_cmd, bool expect_first_reply, + uint8_t *pnum_setup, uint16_t **psetup, + uint32_t *ptotal_param, uint32_t *pnum_param, + uint32_t *pparam_disp, uint8_t **pparam, + uint32_t *ptotal_data, uint32_t *pnum_data, + uint32_t *pdata_disp, uint8_t **pdata) +{ + uint32_t param_ofs, data_ofs; + uint8_t expected_num_setup; + uint32_t max_bytes = UINT32_MAX - bytes_ofs; + uint32_t bytes_end; + + if (num_bytes > max_bytes) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + bytes_end = bytes_ofs + num_bytes; + + if (expect_first_reply) { + if ((wct != 0) || (num_bytes != 0)) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + return NT_STATUS_OK; + } + + switch (smb_cmd) { + case SMBtrans: + case SMBtrans2: + if (wct < 10) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + expected_num_setup = wct - 10; + *ptotal_param = SVAL(vwv + 0, 0); + *ptotal_data = SVAL(vwv + 1, 0); + *pnum_param = SVAL(vwv + 3, 0); + param_ofs = SVAL(vwv + 4, 0); + *pparam_disp = SVAL(vwv + 5, 0); + *pnum_data = SVAL(vwv + 6, 0); + data_ofs = SVAL(vwv + 7, 0); + *pdata_disp = SVAL(vwv + 8, 0); + *pnum_setup = CVAL(vwv + 9, 0); + if (expected_num_setup < (*pnum_setup)) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + *psetup = vwv + 10; + + break; + case SMBnttrans: + if (wct < 18) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + expected_num_setup = wct - 18; + *ptotal_param = IVAL(vwv, 3); + *ptotal_data = IVAL(vwv, 7); + *pnum_param = IVAL(vwv, 11); + param_ofs = IVAL(vwv, 15); + *pparam_disp = IVAL(vwv, 19); + *pnum_data = IVAL(vwv, 23); + data_ofs = IVAL(vwv, 27); + *pdata_disp = IVAL(vwv, 31); + *pnum_setup = CVAL(vwv, 35); + if (expected_num_setup < (*pnum_setup)) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + *psetup = vwv + 18; + break; + + default: + return NT_STATUS_INTERNAL_ERROR; + } + + /* + * Check for buffer overflows. data_ofs needs to be checked against + * the incoming buffer length, data_disp against the total + * length. Likewise for param_ofs/param_disp. + */ + + if (smb_buffer_oob(bytes_end, param_ofs, *pnum_param) + || smb_buffer_oob(*ptotal_param, *pparam_disp, *pnum_param) + || smb_buffer_oob(bytes_end, data_ofs, *pnum_data) + || smb_buffer_oob(*ptotal_data, *pdata_disp, *pnum_data)) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + *pparam = (uint8_t *)inhdr + param_ofs; + *pdata = (uint8_t *)inhdr + data_ofs; + + return NT_STATUS_OK; +} + +static NTSTATUS smb1cli_trans_pull_blob(TALLOC_CTX *mem_ctx, + struct trans_recvblob *blob, + uint32_t total, uint32_t thistime, + uint8_t *buf, uint32_t displacement) +{ + if (blob->data == NULL) { + if (total > blob->max) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + blob->total = total; + blob->data = talloc_array(mem_ctx, uint8_t, total); + if (blob->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + if (total > blob->total) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + if (thistime) { + memcpy(blob->data + displacement, buf, thistime); + blob->received += thistime; + } + + return NT_STATUS_OK; +} + +static void smb1cli_trans_format(struct smb1cli_trans_state *state, + uint8_t *pwct, + int *piov_count) +{ + uint8_t wct = 0; + struct iovec *iov = state->iov; + uint8_t *pad = state->pad; + uint16_t *vwv = state->vwv; + uint32_t param_offset; + uint32_t this_param = 0; + uint32_t param_pad; + uint32_t data_offset; + uint32_t this_data = 0; + uint32_t data_pad; + uint32_t useable_space; + uint8_t cmd; + uint32_t max_trans = smb1cli_conn_max_xmit(state->conn); + + cmd = state->cmd; + + if ((state->param_sent != 0) || (state->data_sent != 0)) { + /* The secondary commands are one after the primary ones */ + cmd += 1; + } + + param_offset = MIN_SMB_SIZE; + + switch (cmd) { + case SMBtrans: + if (smbXcli_conn_use_unicode(state->conn)) { + pad[0] = 0; + iov[0].iov_base = (void *)pad; + iov[0].iov_len = 1; + param_offset += 1; + iov += 1; + } + iov[0].iov_base = (void *)state->pipe_name_conv; + iov[0].iov_len = state->pipe_name_conv_len; + wct = 14 + state->num_setup; + param_offset += iov[0].iov_len; + iov += 1; + break; + case SMBtrans2: + pad[0] = 0; + pad[1] = 'D'; /* Copy this from "old" 3.0 behaviour */ + pad[2] = ' '; + iov[0].iov_base = (void *)pad; + iov[0].iov_len = 3; + wct = 14 + state->num_setup; + param_offset += 3; + iov += 1; + break; + case SMBtranss: + wct = 8; + break; + case SMBtranss2: + wct = 9; + break; + case SMBnttrans: + wct = 19 + state->num_setup; + break; + case SMBnttranss: + wct = 18; + break; + } + + param_offset += wct * sizeof(uint16_t); + useable_space = max_trans - param_offset; + + param_pad = param_offset % 4; + if (param_pad > 0) { + param_pad = MIN(param_pad, useable_space); + iov[0].iov_base = (void *)state->zero_pad; + iov[0].iov_len = param_pad; + iov += 1; + param_offset += param_pad; + } + useable_space = max_trans - param_offset; + + if (state->param_sent < state->num_param) { + this_param = MIN(state->num_param - state->param_sent, + useable_space); + iov[0].iov_base = (void *)(state->param + state->param_sent); + iov[0].iov_len = this_param; + iov += 1; + } + + data_offset = param_offset + this_param; + useable_space = max_trans - data_offset; + + data_pad = data_offset % 4; + if (data_pad > 0) { + data_pad = MIN(data_pad, useable_space); + iov[0].iov_base = (void *)state->zero_pad; + iov[0].iov_len = data_pad; + iov += 1; + data_offset += data_pad; + } + useable_space = max_trans - data_offset; + + if (state->data_sent < state->num_data) { + this_data = MIN(state->num_data - state->data_sent, + useable_space); + iov[0].iov_base = (void *)(state->data + state->data_sent); + iov[0].iov_len = this_data; + iov += 1; + } + + DEBUG(10, ("num_setup=%u, max_setup=%u, " + "param_total=%u, this_param=%u, max_param=%u, " + "data_total=%u, this_data=%u, max_data=%u, " + "param_offset=%u, param_pad=%u, param_disp=%u, " + "data_offset=%u, data_pad=%u, data_disp=%u\n", + (unsigned)state->num_setup, (unsigned)state->max_setup, + (unsigned)state->num_param, (unsigned)this_param, + (unsigned)state->rparam.max, + (unsigned)state->num_data, (unsigned)this_data, + (unsigned)state->rdata.max, + (unsigned)param_offset, (unsigned)param_pad, + (unsigned)state->param_sent, + (unsigned)data_offset, (unsigned)data_pad, + (unsigned)state->data_sent)); + + switch (cmd) { + case SMBtrans: + case SMBtrans2: + SSVAL(vwv + 0, 0, state->num_param); + SSVAL(vwv + 1, 0, state->num_data); + SSVAL(vwv + 2, 0, state->rparam.max); + SSVAL(vwv + 3, 0, state->rdata.max); + SCVAL(vwv + 4, 0, state->max_setup); + SCVAL(vwv + 4, 1, 0); /* reserved */ + SSVAL(vwv + 5, 0, state->flags); + SIVAL(vwv + 6, 0, 0); /* timeout */ + SSVAL(vwv + 8, 0, 0); /* reserved */ + SSVAL(vwv + 9, 0, this_param); + SSVAL(vwv +10, 0, param_offset); + SSVAL(vwv +11, 0, this_data); + SSVAL(vwv +12, 0, data_offset); + SCVAL(vwv +13, 0, state->num_setup); + SCVAL(vwv +13, 1, 0); /* reserved */ + if (state->num_setup > 0) { + memcpy(vwv + 14, state->setup, + sizeof(uint16_t) * state->num_setup); + } + break; + case SMBtranss: + case SMBtranss2: + SSVAL(vwv + 0, 0, state->num_param); + SSVAL(vwv + 1, 0, state->num_data); + SSVAL(vwv + 2, 0, this_param); + SSVAL(vwv + 3, 0, param_offset); + SSVAL(vwv + 4, 0, state->param_sent); + SSVAL(vwv + 5, 0, this_data); + SSVAL(vwv + 6, 0, data_offset); + SSVAL(vwv + 7, 0, state->data_sent); + if (cmd == SMBtranss2) { + SSVAL(vwv + 8, 0, state->fid); + } + break; + case SMBnttrans: + SCVAL(vwv + 0, 0, state->max_setup); + SSVAL(vwv + 0, 1, 0); /* reserved */ + SIVAL(vwv + 1, 1, state->num_param); + SIVAL(vwv + 3, 1, state->num_data); + SIVAL(vwv + 5, 1, state->rparam.max); + SIVAL(vwv + 7, 1, state->rdata.max); + SIVAL(vwv + 9, 1, this_param); + SIVAL(vwv +11, 1, param_offset); + SIVAL(vwv +13, 1, this_data); + SIVAL(vwv +15, 1, data_offset); + SCVAL(vwv +17, 1, state->num_setup); + SSVAL(vwv +18, 0, state->function); + memcpy(vwv + 19, state->setup, + sizeof(uint16_t) * state->num_setup); + break; + case SMBnttranss: + SSVAL(vwv + 0, 0, 0); /* reserved */ + SCVAL(vwv + 1, 0, 0); /* reserved */ + SIVAL(vwv + 1, 1, state->num_param); + SIVAL(vwv + 3, 1, state->num_data); + SIVAL(vwv + 5, 1, this_param); + SIVAL(vwv + 7, 1, param_offset); + SIVAL(vwv + 9, 1, state->param_sent); + SIVAL(vwv +11, 1, this_data); + SIVAL(vwv +13, 1, data_offset); + SIVAL(vwv +15, 1, state->data_sent); + SCVAL(vwv +17, 1, 0); /* reserved */ + break; + } + + state->param_sent += this_param; + state->data_sent += this_data; + + *pwct = wct; + *piov_count = iov - state->iov; +} + +static bool smb1cli_trans_cancel(struct tevent_req *req); +static void smb1cli_trans_done(struct tevent_req *subreq); + +struct tevent_req *smb1cli_trans_send( + TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct smbXcli_conn *conn, uint8_t cmd, + uint8_t additional_flags, uint8_t clear_flags, + uint16_t additional_flags2, uint16_t clear_flags2, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + const char *pipe_name, uint16_t fid, uint16_t function, int flags, + uint16_t *setup, uint8_t num_setup, uint8_t max_setup, + uint8_t *param, uint32_t num_param, uint32_t max_param, + uint8_t *data, uint32_t num_data, uint32_t max_data) +{ + struct tevent_req *req, *subreq; + struct smb1cli_trans_state *state; + int iov_count; + uint8_t wct; + NTSTATUS status; + charset_t charset; + + req = tevent_req_create(mem_ctx, &state, + struct smb1cli_trans_state); + if (req == NULL) { + return NULL; + } + + if ((cmd == SMBtrans) || (cmd == SMBtrans2)) { + if ((num_param > 0xffff) || (max_param > 0xffff) + || (num_data > 0xffff) || (max_data > 0xffff)) { + DEBUG(3, ("Attempt to send invalid trans2 request " + "(setup %u, params %u/%u, data %u/%u)\n", + (unsigned)num_setup, + (unsigned)num_param, (unsigned)max_param, + (unsigned)num_data, (unsigned)max_data)); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + } + + /* + * The largest wct will be for nttrans (19+num_setup). Make sure we + * don't overflow state->vwv in smb1cli_trans_format. + */ + + if ((num_setup + 19) > ARRAY_SIZE(state->vwv)) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + + state->conn = conn; + state->ev = ev; + state->cmd = cmd; + state->additional_flags = additional_flags; + state->clear_flags = clear_flags; + state->additional_flags2 = additional_flags2; + state->clear_flags2 = clear_flags2; + state->timeout_msec = timeout_msec; + state->flags = flags; + state->num_rsetup = 0; + state->rsetup = NULL; + state->pid = pid; + state->tcon = tcon; + state->session = session; + ZERO_STRUCT(state->rparam); + ZERO_STRUCT(state->rdata); + + if (smbXcli_conn_use_unicode(conn)) { + charset = CH_UTF16LE; + } else { + charset = CH_DOS; + } + + if ((pipe_name != NULL) + && (!convert_string_talloc(state, CH_UNIX, charset, + pipe_name, strlen(pipe_name) + 1, + &state->pipe_name_conv, + &state->pipe_name_conv_len))) { + tevent_req_nterror(req, NT_STATUS_NO_MEMORY); + return tevent_req_post(req, ev); + } + state->fid = fid; /* trans2 */ + state->function = function; /* nttrans */ + + state->setup = setup; + state->num_setup = num_setup; + state->max_setup = max_setup; + + state->param = param; + state->num_param = num_param; + state->param_sent = 0; + state->rparam.max = max_param; + + state->data = data; + state->num_data = num_data; + state->data_sent = 0; + state->rdata.max = max_data; + + smb1cli_trans_format(state, &wct, &iov_count); + + subreq = smb1cli_req_create(state, ev, conn, cmd, + state->additional_flags, + state->clear_flags, + state->additional_flags2, + state->clear_flags2, + state->timeout_msec, + state->pid, + state->tcon, + state->session, + wct, state->vwv, + iov_count, state->iov); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + status = smb1cli_req_chain_submit(&subreq, 1); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, state->ev); + } + tevent_req_set_callback(subreq, smb1cli_trans_done, req); + + /* + * Now get the MID of the primary request + * and mark it as persistent. This means + * we will able to send and receive multiple + * SMB pdus using this MID in both directions + * (including correct SMB signing). + */ + state->mid = smb1cli_req_mid(subreq); + smb1cli_req_set_mid(subreq, state->mid); + state->primary_subreq = subreq; + talloc_set_destructor(state, smb1cli_trans_state_destructor); + + tevent_req_set_cancel_fn(req, smb1cli_trans_cancel); + + return req; +} + +static bool smb1cli_trans_cancel(struct tevent_req *req) +{ + struct smb1cli_trans_state *state = + tevent_req_data(req, + struct smb1cli_trans_state); + + if (state->primary_subreq == NULL) { + return false; + } + + return tevent_req_cancel(state->primary_subreq); +} + +static void smb1cli_trans_done2(struct tevent_req *subreq); + +static void smb1cli_trans_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct smb1cli_trans_state *state = + tevent_req_data(req, + struct smb1cli_trans_state); + NTSTATUS status; + bool sent_all; + struct iovec *recv_iov = NULL; + uint8_t *inhdr; + uint8_t wct; + uint16_t *vwv; + uint32_t vwv_ofs; + uint32_t num_bytes; + uint8_t *bytes; + uint32_t bytes_ofs; + uint8_t num_setup = 0; + uint16_t *setup = NULL; + uint32_t total_param = 0; + uint32_t num_param = 0; + uint32_t param_disp = 0; + uint32_t total_data = 0; + uint32_t num_data = 0; + uint32_t data_disp = 0; + uint8_t *param = NULL; + uint8_t *data = NULL; + + status = smb1cli_req_recv(subreq, state, + &recv_iov, + &inhdr, + &wct, + &vwv, + &vwv_ofs, + &num_bytes, + &bytes, + &bytes_ofs, + NULL, /* pinbuf */ + NULL, 0); /* expected */ + /* + * Do not TALLOC_FREE(subreq) here, we might receive more than + * one response for the same mid. + */ + + /* + * We can receive something like STATUS_MORE_ENTRIES, so don't use + * !NT_STATUS_IS_OK(status) here. + */ + + if (NT_STATUS_IS_ERR(status)) { + goto fail; + } + + if (recv_iov == NULL) { + status = NT_STATUS_INVALID_NETWORK_RESPONSE; + goto fail; + } + state->status = status; + + sent_all = ((state->param_sent == state->num_param) + && (state->data_sent == state->num_data)); + + status = smb1cli_pull_trans( + inhdr, wct, vwv, vwv_ofs, + num_bytes, bytes, bytes_ofs, + state->cmd, !sent_all, &num_setup, &setup, + &total_param, &num_param, ¶m_disp, ¶m, + &total_data, &num_data, &data_disp, &data); + + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + + if (!sent_all) { + int iov_count; + struct tevent_req *subreq2; + + smb1cli_trans_format(state, &wct, &iov_count); + + subreq2 = smb1cli_req_create(state, state->ev, state->conn, + state->cmd + 1, + state->additional_flags, + state->clear_flags, + state->additional_flags2, + state->clear_flags2, + state->timeout_msec, + state->pid, + state->tcon, + state->session, + wct, state->vwv, + iov_count, state->iov); + if (tevent_req_nomem(subreq2, req)) { + return; + } + smb1cli_req_set_mid(subreq2, state->mid); + + status = smb1cli_req_chain_submit(&subreq2, 1); + + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + tevent_req_set_callback(subreq2, smb1cli_trans_done2, req); + + return; + } + + status = smb1cli_trans_pull_blob( + state, &state->rparam, total_param, num_param, param, + param_disp); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("Pulling params failed: %s\n", nt_errstr(status))); + goto fail; + } + + status = smb1cli_trans_pull_blob( + state, &state->rdata, total_data, num_data, data, + data_disp); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("Pulling data failed: %s\n", nt_errstr(status))); + goto fail; + } + + if ((state->rparam.total == state->rparam.received) + && (state->rdata.total == state->rdata.received)) { + state->recv_flags2 = SVAL(inhdr, HDR_FLG2); + smb1cli_trans_cleanup_primary(state); + tevent_req_done(req); + return; + } + + TALLOC_FREE(recv_iov); + + return; + + fail: + smb1cli_trans_cleanup_primary(state); + tevent_req_nterror(req, status); +} + +static void smb1cli_trans_done2(struct tevent_req *subreq2) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq2, + struct tevent_req); + struct smb1cli_trans_state *state = + tevent_req_data(req, + struct smb1cli_trans_state); + NTSTATUS status; + bool sent_all; + uint32_t seqnum; + + /* + * First backup the seqnum of the secondary request + * and attach it to the primary request. + */ + seqnum = smb1cli_req_seqnum(subreq2); + smb1cli_req_set_seqnum(state->primary_subreq, seqnum); + + /* This was a one way request */ + status = smb1cli_req_recv(subreq2, state, + NULL, /* recv_iov */ + NULL, /* phdr */ + NULL, /* pwct */ + NULL, /* pvwv */ + NULL, /* pvwv_offset */ + NULL, /* pnum_bytes */ + NULL, /* pbytes */ + NULL, /* pbytes_offset */ + NULL, /* pinbuf */ + NULL, 0); /* expected */ + TALLOC_FREE(subreq2); + + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + + sent_all = ((state->param_sent == state->num_param) + && (state->data_sent == state->num_data)); + + if (!sent_all) { + uint8_t wct; + int iov_count; + + smb1cli_trans_format(state, &wct, &iov_count); + + subreq2 = smb1cli_req_create(state, state->ev, state->conn, + state->cmd + 1, + state->additional_flags, + state->clear_flags, + state->additional_flags2, + state->clear_flags2, + state->timeout_msec, + state->pid, + state->tcon, + state->session, + wct, state->vwv, + iov_count, state->iov); + if (tevent_req_nomem(subreq2, req)) { + return; + } + smb1cli_req_set_mid(subreq2, state->mid); + + status = smb1cli_req_chain_submit(&subreq2, 1); + + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + tevent_req_set_callback(subreq2, smb1cli_trans_done2, req); + return; + } + + return; + + fail: + smb1cli_trans_cleanup_primary(state); + tevent_req_nterror(req, status); +} + +NTSTATUS smb1cli_trans_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + uint16_t *recv_flags2, + uint16_t **setup, uint8_t min_setup, + uint8_t *num_setup, + uint8_t **param, uint32_t min_param, + uint32_t *num_param, + uint8_t **data, uint32_t min_data, + uint32_t *num_data) +{ + struct smb1cli_trans_state *state = + tevent_req_data(req, + struct smb1cli_trans_state); + NTSTATUS status; + + smb1cli_trans_cleanup_primary(state); + + if (tevent_req_is_nterror(req, &status)) { + if (!NT_STATUS_IS_ERR(status)) { + status = NT_STATUS_INVALID_NETWORK_RESPONSE; + } + tevent_req_received(req); + return status; + } + + if ((state->num_rsetup < min_setup) + || (state->rparam.total < min_param) + || (state->rdata.total < min_data)) { + tevent_req_received(req); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + if (recv_flags2 != NULL) { + *recv_flags2 = state->recv_flags2; + } + + if (setup != NULL) { + *setup = talloc_move(mem_ctx, &state->rsetup); + *num_setup = state->num_rsetup; + } else { + TALLOC_FREE(state->rsetup); + } + + if (param != NULL) { + *param = talloc_move(mem_ctx, &state->rparam.data); + *num_param = state->rparam.total; + } else { + TALLOC_FREE(state->rparam.data); + } + + if (data != NULL) { + *data = talloc_move(mem_ctx, &state->rdata.data); + *num_data = state->rdata.total; + } else { + TALLOC_FREE(state->rdata.data); + } + + status = state->status; + tevent_req_received(req); + return status; +} + +NTSTATUS smb1cli_trans(TALLOC_CTX *mem_ctx, struct smbXcli_conn *conn, + uint8_t trans_cmd, + uint8_t additional_flags, uint8_t clear_flags, + uint16_t additional_flags2, uint16_t clear_flags2, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + const char *pipe_name, uint16_t fid, uint16_t function, + int flags, + uint16_t *setup, uint8_t num_setup, uint8_t max_setup, + uint8_t *param, uint32_t num_param, uint32_t max_param, + uint8_t *data, uint32_t num_data, uint32_t max_data, + uint16_t *recv_flags2, + uint16_t **rsetup, uint8_t min_rsetup, uint8_t *num_rsetup, + uint8_t **rparam, uint32_t min_rparam, uint32_t *num_rparam, + uint8_t **rdata, uint32_t min_rdata, uint32_t *num_rdata) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + if (smbXcli_conn_has_async_calls(conn)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER_MIX; + goto fail; + } + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + + req = smb1cli_trans_send(frame, ev, conn, trans_cmd, + additional_flags, clear_flags, + additional_flags2, clear_flags2, + timeout_msec, + pid, tcon, session, + pipe_name, fid, function, flags, + setup, num_setup, max_setup, + param, num_param, max_param, + data, num_data, max_data); + if (req == NULL) { + goto fail; + } + + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + + status = smb1cli_trans_recv(req, mem_ctx, recv_flags2, + rsetup, min_rsetup, num_rsetup, + rparam, min_rparam, num_rparam, + rdata, min_rdata, num_rdata); + fail: + TALLOC_FREE(frame); + return status; +} diff --git a/libcli/smb/smb1cli_write.c b/libcli/smb/smb1cli_write.c new file mode 100644 index 0000000..66f93c0 --- /dev/null +++ b/libcli/smb/smb1cli_write.c @@ -0,0 +1,284 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Gregor Beck 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 "system/network.h" +#include "lib/util/tevent_ntstatus.h" +#include "smb_common.h" +#include "smbXcli_base.h" + +struct smb1cli_writex_state { + uint32_t size; + uint16_t vwv[14]; + uint32_t written; + uint16_t available; + uint8_t pad; + struct iovec iov[2]; +}; + +static void smb1cli_writex_done(struct tevent_req *subreq); + +/** + * Send an asynchrounus SMB_COM_WRITE_ANDX request. + * <a href="http://msdn.microsoft.com/en-us/library/ee441954.aspx">MS-CIFS 2.2.4.43.1</a> + * @see smb1cli_writex_recv(), smb1cli_writex() + * + * @param[in] mem_ctx The memory context for the result. + * @param[in] ev The event context to work on. + * @param[in] conn The smb connection. + * @param[in] timeout_msec If positiv a timeout for the request. + * @param[in] pid The process identifier + * @param[in] tcon The smb tree connect. + * @param[in] session The smb session. + * @param[in] fnum The file id of the file the data should be written to. + * @param[in] mode A bitfield containing the write mode. + * @param[in] buf The data to be written to the file. + * @param[in] offset The offset in bytes from the begin of file where to write. + * @param[in] size The number of bytes to write. + * + * @return a tevent_req or NULL + */ +struct tevent_req *smb1cli_writex_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + uint16_t fnum, + uint16_t mode, + const uint8_t *buf, + uint64_t offset, + uint32_t size) +{ + struct tevent_req *req, *subreq; + struct smb1cli_writex_state *state; + bool bigoffset = ((smb1cli_conn_capabilities(conn) & CAP_LARGE_FILES) != 0); + uint8_t wct = bigoffset ? 14 : 12; + uint16_t *vwv; + uint16_t data_offset = + smb1cli_req_wct_ofs(NULL, 0) /* reqs_before */ + + 1 /* the wct field */ + + wct * 2 /* vwv */ + + 2 /* num_bytes field */ + + 1; /* pad */ + NTSTATUS status; + + req = tevent_req_create(mem_ctx, &state, struct smb1cli_writex_state); + if (req == NULL) { + return NULL; + } + + state->size = size; + + vwv = state->vwv; + + SCVAL(vwv+0, 0, 0xFF); + SCVAL(vwv+0, 1, 0); + SSVAL(vwv+1, 0, 0); + SSVAL(vwv+2, 0, fnum); + SIVAL(vwv+3, 0, offset); + SIVAL(vwv+5, 0, 0); + SSVAL(vwv+7, 0, mode); + SSVAL(vwv+8, 0, 0); + SSVAL(vwv+9, 0, (state->size>>16)); + SSVAL(vwv+10, 0, state->size); + SSVAL(vwv+11, 0, data_offset); + + if (bigoffset) { + SIVAL(vwv+12, 0, (((uint64_t)offset)>>32) & 0xffffffff); + } + + state->pad = 0; + state->iov[0].iov_base = (void *)&state->pad; + state->iov[0].iov_len = 1; + state->iov[1].iov_base = discard_const_p(void, buf); + state->iov[1].iov_len = state->size; + + subreq = smb1cli_req_create(state, ev, conn, SMBwriteX, + 0, 0, /* *_flags */ + 0, 0, /* *_flags2 */ + timeout_msec, pid, tcon, session, + wct, vwv, + ARRAY_SIZE(state->iov), state->iov); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb1cli_writex_done, req); + + status = smb1cli_req_chain_submit(&subreq, 1); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void smb1cli_writex_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct smb1cli_writex_state *state = tevent_req_data( + req, struct smb1cli_writex_state); + struct iovec *recv_iov = NULL; + uint8_t wct; + uint16_t *vwv; + NTSTATUS status; + static const struct smb1cli_req_expected_response expected[] = { + { + .status = NT_STATUS_OK, + .wct = 0x06 + }, + }; + + status = smb1cli_req_recv(subreq, state, + &recv_iov, + NULL, /* phdr */ + &wct, + &vwv, + NULL, /* pvwv_offset */ + NULL, /* num_bytes */ + NULL, /* bytes */ + NULL, /* pbytes_offset */ + NULL, /* inbuf */ + expected, ARRAY_SIZE(expected)); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + state->written = SVAL(vwv+2, 0); + if (state->size > UINT16_MAX) { + /* + * It is important that we only set the + * high bits only if we asked for a large write. + * + * OS/2 print shares get this wrong and may send + * invalid values. + * + * See bug #5326. + */ + state->written |= SVAL(vwv+4, 0)<<16; + } + state->available = SVAL(vwv+3, 0); + + tevent_req_done(req); +} + +/** + * Receive the response to an asynchronous SMB_COM_WRITE_ANDX request. + * <a href="http://msdn.microsoft.com/en-us/library/ee441673.aspx">MS-CIFS:2.2.4.43.2</a> + * + * + * @param[in] req req A tevent request created with smb1cli_writex_send() + * @param[out] pwritten The number of bytes written to the file. + * @param[out] pavailable Valid if writing to a named pipe or IO device. + * + * @return NT_STATUS_OK on succsess. + */ +NTSTATUS smb1cli_writex_recv(struct tevent_req *req, uint32_t *pwritten, uint16_t *pavailable) +{ + struct smb1cli_writex_state *state = tevent_req_data( + req, struct smb1cli_writex_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + return status; + } + if (pwritten != NULL) { + *pwritten = state->written; + } + if (pavailable != NULL) { + *pavailable = state->available; + } + return NT_STATUS_OK; +} + +/** + * Send an synchrounus SMB_COM_WRITE_ANDX request. + * <a href="http://msdn.microsoft.com/en-us/library/ee441848.aspx">MS-CIFS 2.2.4.43</a> + * @see smb1cli_writex_send(), smb1cli_writex_recv() + * + * @param[in] conn The smb connection. + * @param[in] timeout_msec If positiv a timeout for the request. + * @param[in] pid The process identifier + * @param[in] tcon The smb tree connect. + * @param[in] session The smb session. + * @param[in] fnum The file id of the file the data should be written to. + * @param[in] mode A bitfield containing the write mode. + * @param[in] buf The data to be written to the file. + * @param[in] offset The offset in bytes from the begin of file where to write. + * @param[in] size The number of bytes to write. + * @param[out] pwritten The number of bytes written to the file. + * @param[out] pavailable Valid if writing to a named pipe or IO device. + * + * @return NT_STATUS_OK on succsess. + */ +NTSTATUS smb1cli_writex(struct smbXcli_conn *conn, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + uint16_t fnum, + uint16_t mode, + const uint8_t *buf, + uint64_t offset, + uint32_t size, + uint32_t *pwritten, + uint16_t *pavailable) +{ + TALLOC_CTX *frame = NULL; + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_OK; + + frame = talloc_stackframe(); + + if (smbXcli_conn_has_async_calls(conn)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + req = smb1cli_writex_send(frame, ev, conn, + timeout_msec, + pid, tcon, session, + fnum, mode, buf, offset, size); + if (req == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto done; + } + + status = smb1cli_writex_recv(req, pwritten, pavailable); +done: + TALLOC_FREE(frame); + return status; +} diff --git a/libcli/smb/smb2_constants.h b/libcli/smb/smb2_constants.h new file mode 100644 index 0000000..edc8ec0 --- /dev/null +++ b/libcli/smb/smb2_constants.h @@ -0,0 +1,315 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client library header + + Copyright (C) Andrew Tridgell 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __LIBCLI_SMB2_SMB2_CONSTANTS_H__ +#define __LIBCLI_SMB2_SMB2_CONSTANTS_H__ + +/* offsets into SMB2_TRANSFORM header elements */ +#define SMB2_TF_PROTOCOL_ID 0x00 /* 4 bytes */ +#define SMB2_TF_SIGNATURE 0x04 /* 16 bytes */ +#define SMB2_TF_NONCE 0x14 /* 16 bytes */ +#define SMB2_TF_MSG_SIZE 0x24 /* 4 bytes */ +#define SMB2_TF_RESERVED 0x28 /* 2 bytes */ +#define SMB2_TF_FLAGS 0x2A /* 2 bytes */ +#define SMB2_TF_SESSION_ID 0x2C /* 8 bytes */ + +#define SMB2_TF_HDR_SIZE 0x34 /* 52 bytes */ + +#define SMB2_TF_MAGIC 0x424D53FD /* 0xFD 'S' 'M' 'B' */ + +#define SMB2_TF_FLAGS_ENCRYPTED 0x0001 + +/* offsets into header elements for a sync SMB2 request */ +#define SMB2_HDR_PROTOCOL_ID 0x00 +#define SMB2_HDR_LENGTH 0x04 +#define SMB2_HDR_CREDIT_CHARGE 0x06 +#define SMB2_HDR_EPOCH SMB2_HDR_CREDIT_CHARGE /* TODO: remove this */ +#define SMB2_HDR_STATUS 0x08 +#define SMB2_HDR_CHANNEL_SEQUENCE SMB2_HDR_STATUS /* in requests */ +#define SMB2_HDR_OPCODE 0x0c +#define SMB2_HDR_CREDIT 0x0e +#define SMB2_HDR_FLAGS 0x10 +#define SMB2_HDR_NEXT_COMMAND 0x14 +#define SMB2_HDR_MESSAGE_ID 0x18 +#define SMB2_HDR_PID 0x20 +#define SMB2_HDR_TID 0x24 +#define SMB2_HDR_SESSION_ID 0x28 +#define SMB2_HDR_SIGNATURE 0x30 /* 16 bytes */ +#define SMB2_HDR_BODY 0x40 + +/* offsets into header elements for an async SMB2 request */ +#define SMB2_HDR_ASYNC_ID 0x20 + +/* header flags */ +#define SMB2_HDR_FLAG_REDIRECT 0x01 +#define SMB2_HDR_FLAG_ASYNC 0x02 +#define SMB2_HDR_FLAG_CHAINED 0x04 +#define SMB2_HDR_FLAG_SIGNED 0x08 +#define SMB2_HDR_FLAG_PRIORITY_MASK 0x70 +#define SMB2_HDR_FLAG_DFS 0x10000000 +#define SMB2_HDR_FLAG_REPLAY_OPERATION 0x20000000 + +#define SMB2_PRIORITY_MASK_TO_VALUE(__m) (((__m) & SMB2_HDR_FLAG_PRIORITY_MASK) >> 4) +#define SMB2_PRIORITY_VALUE_TO_MASK(__v) (((__v) << 4) & SMB2_HDR_FLAG_PRIORITY_MASK) + +/* SMB2 opcodes */ +#define SMB2_OP_NEGPROT 0x00 +#define SMB2_OP_SESSSETUP 0x01 +#define SMB2_OP_LOGOFF 0x02 +#define SMB2_OP_TCON 0x03 +#define SMB2_OP_TDIS 0x04 +#define SMB2_OP_CREATE 0x05 +#define SMB2_OP_CLOSE 0x06 +#define SMB2_OP_FLUSH 0x07 +#define SMB2_OP_READ 0x08 +#define SMB2_OP_WRITE 0x09 +#define SMB2_OP_LOCK 0x0a +#define SMB2_OP_IOCTL 0x0b +#define SMB2_OP_CANCEL 0x0c +#define SMB2_OP_KEEPALIVE 0x0d +#define SMB2_OP_QUERY_DIRECTORY 0x0e +#define SMB2_OP_NOTIFY 0x0f +#define SMB2_OP_GETINFO 0x10 +#define SMB2_OP_SETINFO 0x11 +#define SMB2_OP_BREAK 0x12 + +#define SMB2_MAGIC 0x424D53FE /* 0xFE 'S' 'M' 'B' */ + +/* SMB2 negotiate dialects */ +#define SMB2_DIALECT_REVISION_000 0x0000 /* early beta dialect */ +#define SMB2_DIALECT_REVISION_202 0x0202 +#define SMB2_DIALECT_REVISION_210 0x0210 +#define SMB2_DIALECT_REVISION_222 0x0222 +#define SMB2_DIALECT_REVISION_224 0x0224 +#define SMB3_DIALECT_REVISION_300 0x0300 +#define SMB3_DIALECT_REVISION_302 0x0302 +#define SMB3_DIALECT_REVISION_310 0x0310 +#define SMB3_DIALECT_REVISION_311 0x0311 +#define SMB2_DIALECT_REVISION_2FF 0x02FF + +/* SMB2 negotiate security_mode */ +#define SMB2_NEGOTIATE_SIGNING_ENABLED 0x01 +#define SMB2_NEGOTIATE_SIGNING_REQUIRED 0x02 + +/* SMB2 global capabilities */ +#define SMB2_CAP_DFS 0x00000001 +#define SMB2_CAP_LEASING 0x00000002 /* only in dialect >= 0x210 */ +#define SMB2_CAP_LARGE_MTU 0x00000004 /* only in dialect >= 0x210 */ +#define SMB2_CAP_MULTI_CHANNEL 0x00000008 /* only in dialect >= 0x222 */ +#define SMB2_CAP_PERSISTENT_HANDLES 0x00000010 /* only in dialect >= 0x222 */ +#define SMB2_CAP_DIRECTORY_LEASING 0x00000020 /* only in dialect >= 0x222 */ +#define SMB2_CAP_ENCRYPTION 0x00000040 /* only in dialect >= 0x222 */ + +/* so we can spot new caps as added */ +#define SMB2_CAP_ALL (\ + SMB2_CAP_DFS | \ + SMB2_CAP_LEASING | \ + SMB2_CAP_LARGE_MTU | \ + SMB2_CAP_MULTI_CHANNEL | \ + SMB2_CAP_PERSISTENT_HANDLES | \ + SMB2_CAP_DIRECTORY_LEASING | \ + SMB2_CAP_ENCRYPTION) + +/* Types of SMB2 Negotiate Contexts - only in dialect >= 0x310 */ +#define SMB2_PREAUTH_INTEGRITY_CAPABILITIES 0x0001 +#define SMB2_ENCRYPTION_CAPABILITIES 0x0002 +#define SMB2_COMPRESSION_CAPABILITIES 0x0003 +#define SMB2_NETNAME_NEGOTIATE_CONTEXT_ID 0x0005 +#define SMB2_TRANSPORT_CAPABILITIES 0x0006 +#define SMB2_RDMA_TRANSFORM_CAPABILITIES 0x0007 +#define SMB2_SIGNING_CAPABILITIES 0x0008 +#define SMB2_POSIX_EXTENSIONS_AVAILABLE 0x0100 + +/* Values for the SMB2_PREAUTH_INTEGRITY_CAPABILITIES Context (>= 0x310) */ +#define SMB2_PREAUTH_INTEGRITY_SHA512 0x0001 + +/* Values for the SMB2_SIGNING_CAPABILITIES Context (>= 0x311) */ +#define SMB2_SIGNING_INVALID_ALGO 0xffff /* only used internally */ +#define SMB2_SIGNING_MD5_SMB1 0xfffe /* internally for SMB1 */ +#define SMB2_SIGNING_HMAC_SHA256 0x0000 /* default <= 0x210 */ +#define SMB2_SIGNING_AES128_CMAC 0x0001 /* default >= 0x224 */ +#define SMB2_SIGNING_AES128_GMAC 0x0002 /* only in dialect >= 0x311 */ + +/* Values for the SMB2_ENCRYPTION_CAPABILITIES Context (>= 0x311) */ +#define SMB2_ENCRYPTION_INVALID_ALGO 0xffff /* only used internally */ +#define SMB2_ENCRYPTION_NONE 0x0000 /* only used internally */ +#define SMB2_ENCRYPTION_AES128_CCM 0x0001 /* only in dialect >= 0x224 */ +#define SMB2_ENCRYPTION_AES128_GCM 0x0002 /* only in dialect >= 0x311 */ +#define SMB2_ENCRYPTION_AES256_CCM 0x0003 /* only in dialect >= 0x311 */ +#define SMB2_ENCRYPTION_AES256_GCM 0x0004 /* only in dialect >= 0x311 */ +#define SMB2_NONCE_HIGH_MAX(nonce_len_bytes) ((uint64_t)(\ + ((nonce_len_bytes) >= 16) ? UINT64_MAX : \ + ((nonce_len_bytes) <= 8) ? 0 : \ + (((uint64_t)1 << (((nonce_len_bytes) - 8)*8)) - 1) \ + )) + +/* Values for the SMB2_TRANSPORT_CAPABILITIES Context (>= 0x311) */ +#define SMB2_ACCEPT_TRANSPORT_LEVEL_SECURITY 0x0001 + +/* Values for the SMB2_RDMA_TRANSFORM_CAPABILITIES Context (>= 0x311) */ +#define SMB2_RDMA_TRANSFORM_NONE 0x0000 +#define SMB2_RDMA_TRANSFORM_ENCRYPTION 0x0001 +#define SMB2_RDMA_TRANSFORM_SIGNING 0x0002 + +/* SMB2 session (request) flags */ +#define SMB2_SESSION_FLAG_BINDING 0x01 +/* SMB2_SESSION_FLAG_ENCRYPT_DATA 0x04 only in dialect >= 0x310 */ + +/* SMB2 session (response) flags */ +#define SMB2_SESSION_FLAG_IS_GUEST 0x0001 +#define SMB2_SESSION_FLAG_IS_NULL 0x0002 +#define SMB2_SESSION_FLAG_ENCRYPT_DATA 0x0004 /* in dialect >= 0x224 */ + +/* SMB2 tree connect (request) flags */ +#define SMB2_SHAREFLAG_CLUSTER_RECONNECT 0x0001 /* only in dialect >= 0x310 */ + +/* SMB2 sharetype flags */ +#define SMB2_SHARE_TYPE_DISK 0x1 +#define SMB2_SHARE_TYPE_PIPE 0x2 +#define SMB2_SHARE_TYPE_PRINT 0x3 + +/* SMB2 share flags */ +#define SMB2_SHAREFLAG_MANUAL_CACHING 0x0000 +#define SMB2_SHAREFLAG_AUTO_CACHING 0x0010 +#define SMB2_SHAREFLAG_VDO_CACHING 0x0020 +#define SMB2_SHAREFLAG_NO_CACHING 0x0030 +#define SMB2_SHAREFLAG_DFS 0x0001 +#define SMB2_SHAREFLAG_DFS_ROOT 0x0002 +#define SMB2_SHAREFLAG_RESTRICT_EXCLUSIVE_OPENS 0x0100 +#define SMB2_SHAREFLAG_FORCE_SHARED_DELETE 0x0200 +#define SMB2_SHAREFLAG_ALLOW_NAMESPACE_CACHING 0x0400 +#define SMB2_SHAREFLAG_ACCESS_BASED_DIRECTORY_ENUM 0x0800 +#define SMB2_SHAREFLAG_FORCE_LEVELII_OPLOCKS 0x1000 +#define SMB2_SHAREFLAG_ENABLE_HASH_V1 0x2000 +#define SMB2_SHAREFLAG_ENABLE_HASH_V2 0x4000 +#define SMB2_SHAREFLAG_ENCRYPT_DATA 0x8000 +#define SMB2_SHAREFLAG_ALL 0xFF33 + +/* SMB2 share capabilities */ +#define SMB2_SHARE_CAP_DFS 0x8 +#define SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY 0x10 /* in dialect >= 0x222 */ +#define SMB2_SHARE_CAP_SCALEOUT 0x20 /* in dialect >= 0x222 */ +#define SMB2_SHARE_CAP_CLUSTER 0x40 /* in dialect >= 0x222 */ +#define SMB2_SHARE_CAP_ASYMMETRIC 0x80 /* in dialect >= 0x302 */ + +/* SMB2 create security flags */ +#define SMB2_SECURITY_DYNAMIC_TRACKING 0x01 +#define SMB2_SECURITY_EFFECTIVE_ONLY 0x02 + +/* SMB2 lock flags */ +#define SMB2_LOCK_FLAG_NONE 0x00000000 +#define SMB2_LOCK_FLAG_SHARED 0x00000001 +#define SMB2_LOCK_FLAG_EXCLUSIVE 0x00000002 +#define SMB2_LOCK_FLAG_UNLOCK 0x00000004 +#define SMB2_LOCK_FLAG_FAIL_IMMEDIATELY 0x00000010 +#define SMB2_LOCK_FLAG_ALL_MASK 0x00000017 + +/* SMB2 requested oplock levels */ +#define SMB2_OPLOCK_LEVEL_NONE 0x00 +#define SMB2_OPLOCK_LEVEL_II 0x01 +#define SMB2_OPLOCK_LEVEL_EXCLUSIVE 0x08 +#define SMB2_OPLOCK_LEVEL_BATCH 0x09 +#define SMB2_OPLOCK_LEVEL_LEASE 0xFF + +/* SMB2 lease bits */ +#define SMB2_LEASE_NONE 0x00 + +/* SMB2 lease flags */ +#define SMB2_LEASE_FLAG_BREAK_IN_PROGRESS 0x00000002 +#define SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET 0x00000004 + +/* SMB2 lease break flags */ +#define SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED 0x01 + +/* SMB2 impersonation levels */ +#define SMB2_IMPERSONATION_ANONYMOUS 0x00 +#define SMB2_IMPERSONATION_IDENTIFICATION 0x01 +#define SMB2_IMPERSONATION_IMPERSONATION 0x02 +#define SMB2_IMPERSONATION_DELEGATE 0x03 + +/* SMB2 create tags */ +#define SMB2_CREATE_TAG_EXTA "ExtA" +#define SMB2_CREATE_TAG_MXAC "MxAc" +#define SMB2_CREATE_TAG_SECD "SecD" +#define SMB2_CREATE_TAG_DHNQ "DHnQ" +#define SMB2_CREATE_TAG_DHNC "DHnC" +#define SMB2_CREATE_TAG_ALSI "AlSi" +#define SMB2_CREATE_TAG_TWRP "TWrp" +#define SMB2_CREATE_TAG_QFID "QFid" +#define SMB2_CREATE_TAG_RQLS "RqLs" +#define SMB2_CREATE_TAG_DH2Q "DH2Q" +#define SMB2_CREATE_TAG_DH2C "DH2C" +#define SMB2_CREATE_TAG_AAPL "AAPL" +#define SMB2_CREATE_TAG_APP_INSTANCE_ID "\x45\xBC\xA6\x6A\xEF\xA7\xF7\x4A\x90\x08\xFA\x46\x2E\x14\x4D\x74" +#define SVHDX_OPEN_DEVICE_CONTEXT "\x9C\xCB\xCF\x9E\x04\xC1\xE6\x43\x98\x0E\x15\x8D\xA1\xF6\xEC\x83" +#define SMB2_CREATE_TAG_POSIX "\x93\xAD\x25\x50\x9C\xB4\x11\xE7\xB4\x23\x83\xDE\x96\x8B\xCD\x7C" + +/* SMB2 notify flags */ +#define SMB2_WATCH_TREE 0x0001 + +/* SMB2 Create ignore some more create_options */ +#define SMB2_CREATE_OPTIONS_NOT_SUPPORTED_MASK (NTCREATEX_OPTIONS_TREE_CONNECTION | \ + NTCREATEX_OPTIONS_OPFILTER) + +/* + SMB2 uses different level numbers for the same old SMB trans2 search levels +*/ +#define SMB2_FIND_DIRECTORY_INFO 0x01 +#define SMB2_FIND_FULL_DIRECTORY_INFO 0x02 +#define SMB2_FIND_BOTH_DIRECTORY_INFO 0x03 +#define SMB2_FIND_NAME_INFO 0x0C +#define SMB2_FIND_ID_BOTH_DIRECTORY_INFO 0x25 +#define SMB2_FIND_ID_FULL_DIRECTORY_INFO 0x26 + +/* SMB2 UNIX Extensions. */ +#define SMB2_FIND_POSIX_INFORMATION 0x64 + +/* flags for SMB2 find */ +#define SMB2_CONTINUE_FLAG_RESTART 0x01 +#define SMB2_CONTINUE_FLAG_SINGLE 0x02 +#define SMB2_CONTINUE_FLAG_INDEX 0x04 +#define SMB2_CONTINUE_FLAG_REOPEN 0x10 + +/* get/setinfo classes, see [MS-SMB2] 2.2.37 and 2.2.39 */ +#define SMB2_0_INFO_FILE 0x01 +#define SMB2_0_INFO_FILESYSTEM 0x02 +#define SMB2_0_INFO_SECURITY 0x03 +#define SMB2_0_INFO_QUOTA 0x04 + +#define SMB2_CLOSE_FLAGS_FULL_INFORMATION (0x01) + +#define SMB2_READFLAG_READ_UNBUFFERED 0x01 + +#define SMB2_WRITEFLAG_WRITE_THROUGH 0x00000001 +#define SMB2_WRITEFLAG_WRITE_UNBUFFERED 0x00000002 + +/* 2.2.31 SMB2 IOCTL Request */ +#define SMB2_IOCTL_FLAG_IS_FSCTL 0x00000001 + +/* + * Flags for durable handle v2 requests + */ +#define SMB2_DHANDLE_FLAG_PERSISTENT 0x00000002 + +/* The AES CCM nonce N of 15 - L octets. Where L=4 */ +#define SMB2_AES_128_CCM_NONCE_SIZE 11 + +#endif diff --git a/libcli/smb/smb2_create_blob.c b/libcli/smb/smb2_create_blob.c new file mode 100644 index 0000000..ecd61e0 --- /dev/null +++ b/libcli/smb/smb2_create_blob.c @@ -0,0 +1,242 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 Create Context Blob handling + + Copyright (C) Andrew Tridgell 2005 + Copyright (C) Stefan Metzmacher 2008-2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "../libcli/smb/smb_common.h" + +static size_t smb2_create_blob_padding(uint32_t offset, size_t n) +{ + if ((offset & (n-1)) == 0) return 0; + return n - (offset & (n-1)); +} + +/* + parse a set of SMB2 create blobs +*/ +NTSTATUS smb2_create_blob_parse(TALLOC_CTX *mem_ctx, const DATA_BLOB buffer, + struct smb2_create_blobs *blobs) +{ + const uint8_t *data = buffer.data; + uint32_t remaining = buffer.length; + + while (remaining > 0) { + uint32_t next; + uint32_t name_offset, name_length; + uint32_t data_offset; + uint32_t data_length; + char *tag; + DATA_BLOB b; + NTSTATUS status; + + if (remaining < 16) { + return NT_STATUS_INVALID_PARAMETER; + } + next = IVAL(data, 0); + name_offset = SVAL(data, 4); + name_length = SVAL(data, 6); +#if 0 + reserved = SVAL(data, 8); +#endif + data_offset = SVAL(data, 10); + data_length = IVAL(data, 12); + + if ((next & 0x7) != 0 || + next > remaining || + name_offset != 16 || + name_length < 4 || + name_offset + name_length > remaining || + (data_offset & 0x7) != 0 || + (data_offset && (data_offset < name_offset + name_length)) || + (data_offset > remaining) || + (data_offset + (uint64_t)data_length > remaining)) { + return NT_STATUS_INVALID_PARAMETER; + } + + tag = talloc_strndup(mem_ctx, (const char *)data + name_offset, name_length); + if (tag == NULL) { + return NT_STATUS_NO_MEMORY; + } + + b = data_blob_const(data+data_offset, data_length); + status = smb2_create_blob_add(mem_ctx, blobs, tag, b); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + talloc_free(tag); + + if (next == 0) break; + + remaining -= next; + data += next; + + if (remaining < 16) { + return NT_STATUS_INVALID_PARAMETER; + } + } + + return NT_STATUS_OK; +} + + +/* + add a blob to a smb2_create attribute blob +*/ +static NTSTATUS smb2_create_blob_push_one(TALLOC_CTX *mem_ctx, DATA_BLOB *buffer, + const struct smb2_create_blob *blob, + bool last) +{ + uint32_t ofs = buffer->length; + size_t tag_length = strlen(blob->tag); + size_t blob_offset = 0; + size_t blob_pad = 0; + size_t next_offset = 0; + size_t next_pad = 0; + bool ok; + + blob_offset = 0x10 + tag_length; + blob_pad = smb2_create_blob_padding(blob_offset, 8); + next_offset = blob_offset + blob_pad + blob->data.length; + if (!last) { + next_pad = smb2_create_blob_padding(next_offset, 8); + } + + ok = data_blob_realloc(mem_ctx, buffer, + buffer->length + next_offset + next_pad); + if (!ok) { + return NT_STATUS_NO_MEMORY; + } + + if (last) { + SIVAL(buffer->data, ofs+0x00, 0); + } else { + SIVAL(buffer->data, ofs+0x00, next_offset + next_pad); + } + SSVAL(buffer->data, ofs+0x04, 0x10); /* offset of tag */ + SIVAL(buffer->data, ofs+0x06, tag_length); /* tag length */ + SSVAL(buffer->data, ofs+0x0A, blob_offset + blob_pad); /* offset of data */ + SIVAL(buffer->data, ofs+0x0C, blob->data.length); + memcpy(buffer->data+ofs+0x10, blob->tag, tag_length); + if (blob_pad > 0) { + memset(buffer->data+ofs+blob_offset, 0, blob_pad); + blob_offset += blob_pad; + } + memcpy(buffer->data+ofs+blob_offset, blob->data.data, blob->data.length); + if (next_pad > 0) { + memset(buffer->data+ofs+next_offset, 0, next_pad); + } + + return NT_STATUS_OK; +} + + +/* + create a buffer of a set of create blobs +*/ +NTSTATUS smb2_create_blob_push(TALLOC_CTX *mem_ctx, DATA_BLOB *buffer, + const struct smb2_create_blobs blobs) +{ + uint32_t i; + NTSTATUS status; + + *buffer = (DATA_BLOB) { 0 }; + for (i=0; i < blobs.num_blobs; i++) { + bool last = false; + const struct smb2_create_blob *c; + + if ((i + 1) == blobs.num_blobs) { + last = true; + } + + c = &blobs.blobs[i]; + status = smb2_create_blob_push_one(mem_ctx, buffer, c, last); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + return NT_STATUS_OK; +} + + +NTSTATUS smb2_create_blob_add(TALLOC_CTX *mem_ctx, struct smb2_create_blobs *b, + const char *tag, DATA_BLOB data) +{ + struct smb2_create_blob *array; + + array = talloc_realloc(mem_ctx, b->blobs, + struct smb2_create_blob, + b->num_blobs + 1); + NT_STATUS_HAVE_NO_MEMORY(array); + b->blobs = array; + + b->blobs[b->num_blobs].tag = talloc_strdup(b->blobs, tag); + NT_STATUS_HAVE_NO_MEMORY(b->blobs[b->num_blobs].tag); + + if (data.data) { + b->blobs[b->num_blobs].data = data_blob_talloc(b->blobs, + data.data, + data.length); + NT_STATUS_HAVE_NO_MEMORY(b->blobs[b->num_blobs].data.data); + } else { + b->blobs[b->num_blobs].data = data_blob_null; + } + + b->num_blobs += 1; + + return NT_STATUS_OK; +} + +/* + * return the first blob with the given tag + */ +struct smb2_create_blob *smb2_create_blob_find(const struct smb2_create_blobs *b, + const char *tag) +{ + uint32_t i; + + if (b == NULL) { + return NULL; + } + + for (i=0; i < b->num_blobs; i++) { + if (strcmp(b->blobs[i].tag, tag) == 0) { + return &b->blobs[i]; + } + } + + return NULL; +} + +void smb2_create_blob_remove(struct smb2_create_blobs *b, const char *tag) +{ + struct smb2_create_blob *blob = smb2_create_blob_find(b, tag); + + if (blob == NULL) { + return; + } + + TALLOC_FREE(blob->tag); + data_blob_free(&blob->data); + + *blob = b->blobs[b->num_blobs-1]; + b->num_blobs -= 1; +} diff --git a/libcli/smb/smb2_create_blob.h b/libcli/smb/smb2_create_blob.h new file mode 100644 index 0000000..f41e77e --- /dev/null +++ b/libcli/smb/smb2_create_blob.h @@ -0,0 +1,76 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 Create Context Blob handling + + Copyright (C) Andrew Tridgell 2005 + Copyright (C) Stefan Metzmacher 2008-2009 + + 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 _LIBCLI_SMB_SMB2_CREATE_BLOB_H_ +#define _LIBCLI_SMB_SMB2_CREATE_BLOB_H_ + +#include "replace.h" +#include "lib/util/data_blob.h" +#include "lib/util/time.h" +#include "libcli/util/ntstatus.h" + +struct smb2_create_blob { + char *tag; + DATA_BLOB data; +}; + +struct smb2_create_blobs { + uint32_t num_blobs; + struct smb2_create_blob *blobs; +}; + +struct smb_create_returns { + uint8_t oplock_level; + uint32_t create_action; + NTTIME creation_time; + NTTIME last_access_time; + NTTIME last_write_time; + NTTIME change_time; + uint64_t allocation_size; + uint64_t end_of_file; + uint32_t file_attributes; +}; + +/* + parse a set of SMB2 create blobs +*/ +NTSTATUS smb2_create_blob_parse(TALLOC_CTX *mem_ctx, const DATA_BLOB buffer, + struct smb2_create_blobs *blobs); + +/* + create a buffer of a set of create blobs +*/ +NTSTATUS smb2_create_blob_push(TALLOC_CTX *mem_ctx, DATA_BLOB *buffer, + const struct smb2_create_blobs blobs); + +NTSTATUS smb2_create_blob_add(TALLOC_CTX *mem_ctx, struct smb2_create_blobs *b, + const char *tag, DATA_BLOB data); + +/* + * return the first blob with the given tag + */ +struct smb2_create_blob *smb2_create_blob_find(const struct smb2_create_blobs *b, + const char *tag); + +void smb2_create_blob_remove(struct smb2_create_blobs *b, const char *tag); + +#endif /* _LIBCLI_SMB_SMB2_CREATE_BLOB_H_ */ diff --git a/libcli/smb/smb2_create_ctx.h b/libcli/smb/smb2_create_ctx.h new file mode 100644 index 0000000..7e7bf22 --- /dev/null +++ b/libcli/smb/smb2_create_ctx.h @@ -0,0 +1,47 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 create context specific stuff + + Copyright (C) Ralph Boehme 2014 + + 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 __LIBCLI_SMB2_CREATE_CTX_H__ +#define __LIBCLI_SMB2_CREATE_CTX_H__ + +/* http://opensource.apple.com/source/smb/smb-697.1.1/kernel/netsmb/smb_2.h */ + +/* "AAPL" Context Command Codes */ +#define SMB2_CRTCTX_AAPL_SERVER_QUERY 1 +#define SMB2_CRTCTX_AAPL_RESOLVE_ID 2 + +/* "AAPL" Server Query request/response bitmap */ +#define SMB2_CRTCTX_AAPL_SERVER_CAPS 1 +#define SMB2_CRTCTX_AAPL_VOLUME_CAPS 2 +#define SMB2_CRTCTX_AAPL_MODEL_INFO 4 + +/* "AAPL" Client/Server Capabilities bitmap */ +#define SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR 1 +#define SMB2_CRTCTX_AAPL_SUPPORTS_OSX_COPYFILE 2 +#define SMB2_CRTCTX_AAPL_UNIX_BASED 4 +#define SMB2_CRTCTX_AAPL_SUPPORTS_NFS_ACE 8 + +/* "AAPL" Volume Capabilities bitmap */ +#define SMB2_CRTCTX_AAPL_SUPPORT_RESOLVE_ID 1 +#define SMB2_CRTCTX_AAPL_CASE_SENSITIVE 2 +#define SMB2_CRTCTX_AAPL_FULL_SYNC 4 + +#endif diff --git a/libcli/smb/smb2_lease.c b/libcli/smb/smb2_lease.c new file mode 100644 index 0000000..fc641ff --- /dev/null +++ b/libcli/smb/smb2_lease.c @@ -0,0 +1,102 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 Lease context handling + + Copyright (C) Stefan Metzmacher 2012 + Copyright (C) Volker Lendecke 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 "../libcli/smb/smb_common.h" + +ssize_t smb2_lease_pull(const uint8_t *buf, size_t len, + struct smb2_lease *lease) +{ + int version; + + switch (len) { + case 32: + version = 1; + break; + case 52: + version = 2; + break; + default: + return -1; + } + + memcpy(&lease->lease_key, buf, 16); + lease->lease_state = IVAL(buf, 16); + lease->lease_flags = IVAL(buf, 20); + lease->lease_duration = BVAL(buf, 24); + lease->lease_version = version; + + switch (version) { + case 1: + ZERO_STRUCT(lease->parent_lease_key); + lease->lease_epoch = 0; + break; + case 2: + memcpy(&lease->parent_lease_key, buf+32, 16); + lease->lease_epoch = SVAL(buf, 48); + break; + } + + return len; +} + +bool smb2_lease_push(const struct smb2_lease *lease, uint8_t *buf, size_t len) +{ + int version; + + switch (len) { + case 32: + version = 1; + break; + case 52: + version = 2; + break; + default: + return false; + } + + memcpy(&buf[0], &lease->lease_key, 16); + SIVAL(buf, 16, lease->lease_state); + SIVAL(buf, 20, lease->lease_flags); + SBVAL(buf, 24, lease->lease_duration); + + if (version == 2) { + memcpy(&buf[32], &lease->parent_lease_key, 16); + SIVAL(buf, 48, lease->lease_epoch); + } + + return true; +} + +bool smb2_lease_key_equal(const struct smb2_lease_key *k1, + const struct smb2_lease_key *k2) +{ + return ((k1->data[0] == k2->data[0]) && (k1->data[1] == k2->data[1])); +} + +bool smb2_lease_equal(const struct GUID *g1, + const struct smb2_lease_key *k1, + const struct GUID *g2, + const struct smb2_lease_key *k2) +{ + return GUID_equal(g1, g2) && smb2_lease_key_equal(k1, k2); +} diff --git a/libcli/smb/smb2_lease.h b/libcli/smb/smb2_lease.h new file mode 100644 index 0000000..2e6faf7 --- /dev/null +++ b/libcli/smb/smb2_lease.h @@ -0,0 +1,43 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 Lease context handling + + Copyright (C) Stefan Metzmacher 2012 + Copyright (C) Volker Lendecke 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/>. +*/ + +#ifndef _LIBCLI_SMB_SMB2_LEASE_H_ +#define _LIBCLI_SMB_SMB2_LEASE_H_ + +#include "librpc/gen_ndr/ndr_misc.h" +#include "librpc/gen_ndr/smb2_lease_struct.h" + +/* + * Parse a smb2 lease create context. Return -1 on error, buffer.length on + * success. V1 and V2 differ only by length of buffer.length + */ +ssize_t smb2_lease_pull(const uint8_t *buf, size_t len, + struct smb2_lease *lease); +bool smb2_lease_push(const struct smb2_lease *lease, uint8_t *buf, size_t len); +bool smb2_lease_key_equal(const struct smb2_lease_key *k1, + const struct smb2_lease_key *k2); +bool smb2_lease_equal(const struct GUID *g1, + const struct smb2_lease_key *k1, + const struct GUID *g2, + const struct smb2_lease_key *k2); + +#endif /* _LIBCLI_SMB_SMB2_LEASE_H_ */ diff --git a/libcli/smb/smb2_lock.h b/libcli/smb/smb2_lock.h new file mode 100644 index 0000000..f0e0535 --- /dev/null +++ b/libcli/smb/smb2_lock.h @@ -0,0 +1,32 @@ +/* + * Unix SMB/CIFS implementation. + * + * Copyright (C) Volker Lendecke 2019 + * + * 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 __LIBCLI_SMB_SMB2_LOCK_H__ +#define __LIBCLI_SMB_SMB2_LOCK_H__ + +#include "replace.h" + +struct smb2_lock_element { + uint64_t offset; + uint64_t length; + uint32_t flags; + uint32_t reserved; +}; + +#endif diff --git a/libcli/smb/smb2_negotiate_context.c b/libcli/smb/smb2_negotiate_context.c new file mode 100644 index 0000000..9ec20bc --- /dev/null +++ b/libcli/smb/smb2_negotiate_context.c @@ -0,0 +1,203 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2014 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "../libcli/smb/smb_common.h" +#include "libcli/smb/smb2_negotiate_context.h" + +static size_t smb2_negotiate_context_padding(uint32_t offset, size_t n) +{ + if ((offset & (n-1)) == 0) return 0; + return n - (offset & (n-1)); +} + +/* + parse a set of SMB2 create contexts +*/ +NTSTATUS smb2_negotiate_context_parse(TALLOC_CTX *mem_ctx, const DATA_BLOB buffer, + uint16_t expected_count, + struct smb2_negotiate_contexts *contexts) +{ + const uint8_t *data = buffer.data; + uint32_t remaining = buffer.length; + uint16_t idx; + + for (idx = 0; idx < expected_count; idx++) { + uint16_t data_length; + uint16_t type; + NTSTATUS status; + size_t pad; + uint32_t next_offset; + + if (remaining < 8) { + return NT_STATUS_INVALID_PARAMETER; + } + type = SVAL(data, 0x00); + data_length = SVAL(data, 0x02); +#if 0 + reserved = IVAL(data, 0x04); +#endif + + next_offset = 0x08 + data_length; + if (remaining < next_offset) { + return NT_STATUS_INVALID_PARAMETER; + } + + status = smb2_negotiate_context_add( + mem_ctx, contexts, type, data+0x08, data_length); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (contexts->num_contexts == expected_count) { + break; + } + + remaining -= next_offset; + data += next_offset; + + if (remaining == 0) { + break; + } + + pad = smb2_negotiate_context_padding(next_offset, 8); + if (remaining < pad) { + return NT_STATUS_INVALID_PARAMETER; + } + remaining -= pad; + data += pad; + } + + if (contexts->num_contexts != expected_count) { + return NT_STATUS_INVALID_PARAMETER; + } + + return NT_STATUS_OK; +} + +/* + add a context to a smb2_negotiate attribute context +*/ +static NTSTATUS smb2_negotiate_context_push_one(TALLOC_CTX *mem_ctx, DATA_BLOB *buffer, + const struct smb2_negotiate_context *context, + bool last) +{ + uint32_t ofs = buffer->length; + size_t next_offset = 0; + size_t next_pad = 0; + bool ok; + + if (context->data.length > UINT16_MAX) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + next_offset = 0x08 + context->data.length; + if (!last) { + next_pad = smb2_negotiate_context_padding(next_offset, 8); + } + + ok = data_blob_realloc(mem_ctx, buffer, + buffer->length + next_offset + next_pad); + if (!ok) { + return NT_STATUS_NO_MEMORY; + } + + SSVAL(buffer->data, ofs+0x00, context->type); + SIVAL(buffer->data, ofs+0x02, context->data.length); + SIVAL(buffer->data, ofs+0x04, 0); + memcpy(buffer->data+ofs+0x08, context->data.data, context->data.length); + if (next_pad > 0) { + memset(buffer->data+ofs+next_offset, 0, next_pad); + } + + return NT_STATUS_OK; +} + +/* + create a buffer of a set of create contexts +*/ +NTSTATUS smb2_negotiate_context_push(TALLOC_CTX *mem_ctx, DATA_BLOB *buffer, + const struct smb2_negotiate_contexts contexts) +{ + uint32_t i; + NTSTATUS status; + + *buffer = data_blob(NULL, 0); + for (i=0; i < contexts.num_contexts; i++) { + bool last = false; + const struct smb2_negotiate_context *c; + + if ((i + 1) == contexts.num_contexts) { + last = true; + } + + c = &contexts.contexts[i]; + status = smb2_negotiate_context_push_one(mem_ctx, buffer, c, last); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + return NT_STATUS_OK; +} + +NTSTATUS smb2_negotiate_context_add(TALLOC_CTX *mem_ctx, + struct smb2_negotiate_contexts *c, + uint16_t type, + const uint8_t *buf, + size_t buflen) +{ + struct smb2_negotiate_context *array; + + array = talloc_realloc(mem_ctx, c->contexts, + struct smb2_negotiate_context, + c->num_contexts + 1); + NT_STATUS_HAVE_NO_MEMORY(array); + c->contexts = array; + + c->contexts[c->num_contexts].type = type; + + if (buf != NULL) { + c->contexts[c->num_contexts].data = data_blob_talloc( + c->contexts, buf, buflen); + NT_STATUS_HAVE_NO_MEMORY(c->contexts[c->num_contexts].data.data); + } else { + c->contexts[c->num_contexts].data = data_blob_null; + } + + c->num_contexts += 1; + + return NT_STATUS_OK; +} + +/* + * return the first blob with the given tag + */ +struct smb2_negotiate_context *smb2_negotiate_context_find(const struct smb2_negotiate_contexts *c, + uint16_t type) +{ + uint32_t i; + + for (i=0; i < c->num_contexts; i++) { + if (c->contexts[i].type == type) { + return &c->contexts[i]; + } + } + + return NULL; +} diff --git a/libcli/smb/smb2_negotiate_context.h b/libcli/smb/smb2_negotiate_context.h new file mode 100644 index 0000000..645fb64 --- /dev/null +++ b/libcli/smb/smb2_negotiate_context.h @@ -0,0 +1,92 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2014 + + 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 _LIBCLI_SMB_SMB2_NEGOTIATE_BLOB_H_ +#define _LIBCLI_SMB_SMB2_NEGOTIATE_BLOB_H_ + +struct smb2_negotiate_context { + uint16_t type; + DATA_BLOB data; +}; + +struct smb2_negotiate_contexts { + uint16_t num_contexts; + struct smb2_negotiate_context *contexts; +}; + +/* + parse a set of SMB2 negotiate contexts +*/ +NTSTATUS smb2_negotiate_context_parse(TALLOC_CTX *mem_ctx, const DATA_BLOB buffer, + uint16_t expected_count, + struct smb2_negotiate_contexts *contexts); + +/* + negotiate a buffer of a set of negotiate contexts +*/ +NTSTATUS smb2_negotiate_context_push(TALLOC_CTX *mem_ctx, DATA_BLOB *buffer, + const struct smb2_negotiate_contexts contexts); + +NTSTATUS smb2_negotiate_context_add(TALLOC_CTX *mem_ctx, + struct smb2_negotiate_contexts *c, + uint16_t type, + const uint8_t *buf, + size_t buflen); + +/* + * return the first context with the given tag + */ +struct smb2_negotiate_context *smb2_negotiate_context_find(const struct smb2_negotiate_contexts *b, + uint16_t type); +#define WINDOWS_CLIENT_PURE_SMB2_NEGPROT_INITIAL_CREDIT_ASK 31 + +struct smb3_signing_capabilities { +#define SMB3_SIGNING_CAPABILITIES_MAX_ALGOS 3 + uint16_t num_algos; + uint16_t algos[SMB3_SIGNING_CAPABILITIES_MAX_ALGOS]; +}; + +struct smb3_encryption_capabilities { +#define SMB3_ENCRYTION_CAPABILITIES_MAX_ALGOS 4 + uint16_t num_algos; + uint16_t algos[SMB3_ENCRYTION_CAPABILITIES_MAX_ALGOS]; +}; + +struct smb311_capabilities { + struct smb3_signing_capabilities signing; + struct smb3_encryption_capabilities encryption; +}; + +const char *smb3_signing_algorithm_name(uint16_t algo); +const char *smb3_encryption_algorithm_name(uint16_t algo); + +struct smb311_capabilities smb311_capabilities_parse(const char *role, + const char * const *signing_algos, + const char * const *encryption_algos); + +NTSTATUS smb311_capabilities_check(const struct smb311_capabilities *c, + const char *debug_prefix, + int debug_lvl, + NTSTATUS error_status, + const char *role, + enum protocol_types protocol, + uint16_t sign_algo, + uint16_t cipher_algo); + +#endif /* _LIBCLI_SMB_SMB2_NEGOTIATE_BLOB_H_ */ diff --git a/libcli/smb/smb2_posix.c b/libcli/smb/smb2_posix.c new file mode 100644 index 0000000..60be321 --- /dev/null +++ b/libcli/smb/smb2_posix.c @@ -0,0 +1,51 @@ +/* + * Unix SMB/CIFS implementation. + * + * SMB2 Posix context handling + * + * Copyright (C) Jeremy Allison 2019 + * + * 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 "replace.h" +#include "libcli/smb/smb2_posix.h" +#include "libcli/smb/smb2_constants.h" +#include "lib/util/byteorder.h" + +NTSTATUS make_smb2_posix_create_ctx( + TALLOC_CTX *mem_ctx, + struct smb2_create_blobs **crb, + mode_t mode) +{ + struct smb2_create_blobs *cblobs = NULL; + uint8_t linear_mode[4]; + DATA_BLOB blob = { .data=linear_mode, .length=sizeof(linear_mode) }; + NTSTATUS status; + + cblobs = talloc_zero(mem_ctx, struct smb2_create_blobs); + if (cblobs == NULL) { + return NT_STATUS_NO_MEMORY; + } + SIVAL(&linear_mode,0, unix_perms_to_wire(mode & ~S_IFMT)); + + status = smb2_create_blob_add( + cblobs, cblobs, SMB2_CREATE_TAG_POSIX, blob); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(cblobs); + return status; + } + *crb = cblobs; + return NT_STATUS_OK; +} diff --git a/libcli/smb/smb2_posix.h b/libcli/smb/smb2_posix.h new file mode 100644 index 0000000..0751814 --- /dev/null +++ b/libcli/smb/smb2_posix.h @@ -0,0 +1,36 @@ +/* + * Unix SMB/CIFS implementation. + * + * SMB2 Posix context handling + * + * Copyright (C) Jeremy Allison 2019 + * + * 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 _LIBCLI_SMB_SMB2_POSIX_H_ +#define _LIBCLI_SMB_SMB2_POSIX_H_ + +#include "replace.h" +#include "system/filesys.h" +#include <talloc.h> +#include "libcli/smb/smb2_create_blob.h" +#include "libcli/smb/smb_util.h" + +NTSTATUS make_smb2_posix_create_ctx( + TALLOC_CTX *mem_ctx, + struct smb2_create_blobs **crb, + mode_t mode); + +#endif /* _LIBCLI_SMB_SMB2_POSIX_H_ */ diff --git a/libcli/smb/smb2_signing.c b/libcli/smb/smb2_signing.c new file mode 100644 index 0000000..d95274c --- /dev/null +++ b/libcli/smb/smb2_signing.c @@ -0,0 +1,1301 @@ +/* + Unix SMB/CIFS implementation. + SMB2 signing + + Copyright (C) Stefan Metzmacher 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/filesys.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> +#define SMB2_SIGNING_KEY_GNUTLS_TYPES 1 +#include "../libcli/smb/smb_common.h" +#include "../lib/crypto/crypto.h" +#include "lib/util/iov_buf.h" + +#ifndef HAVE_GNUTLS_AES_CMAC +#include "lib/crypto/aes.h" +#include "lib/crypto/aes_cmac_128.h" +#endif + +#include "lib/crypto/gnutls_helpers.h" + +void smb2_signing_derivations_fill_const_stack(struct smb2_signing_derivations *ds, + enum protocol_types protocol, + const DATA_BLOB preauth_hash) +{ + *ds = (struct smb2_signing_derivations) { .signing = NULL, }; + + if (protocol >= PROTOCOL_SMB3_11) { + struct smb2_signing_derivation *d = NULL; + + SMB_ASSERT(preauth_hash.length != 0); + + d = &ds->__signing; + ds->signing = d; + d->label = data_blob_string_const_null("SMBSigningKey"); + d->context = preauth_hash; + + d = &ds->__cipher_c2s; + ds->cipher_c2s = d; + d->label = data_blob_string_const_null("SMBC2SCipherKey"); + d->context = preauth_hash; + + d = &ds->__cipher_s2c; + ds->cipher_s2c = d; + d->label = data_blob_string_const_null("SMBS2CCipherKey"); + d->context = preauth_hash; + + d = &ds->__application; + ds->application = d; + d->label = data_blob_string_const_null("SMBAppKey"); + d->context = preauth_hash; + + } else if (protocol >= PROTOCOL_SMB3_00) { + struct smb2_signing_derivation *d = NULL; + + d = &ds->__signing; + ds->signing = d; + d->label = data_blob_string_const_null("SMB2AESCMAC"); + d->context = data_blob_string_const_null("SmbSign"); + + d = &ds->__cipher_c2s; + ds->cipher_c2s = d; + d->label = data_blob_string_const_null("SMB2AESCCM"); + d->context = data_blob_string_const_null("ServerIn "); + + d = &ds->__cipher_s2c; + ds->cipher_s2c = d; + d->label = data_blob_string_const_null("SMB2AESCCM"); + d->context = data_blob_string_const_null("ServerOut"); + + d = &ds->__application; + ds->application = d; + d->label = data_blob_string_const_null("SMB2APP"); + d->context = data_blob_string_const_null("SmbRpc"); + } +} + +static int smb2_signing_key_destructor(struct smb2_signing_key *key) +{ + if (key->hmac_hnd != NULL) { + gnutls_hmac_deinit(key->hmac_hnd, NULL); + key->hmac_hnd = NULL; + } + + if (key->cipher_hnd != NULL) { + gnutls_aead_cipher_deinit(key->cipher_hnd); + key->cipher_hnd = NULL; + } + + return 0; +} + +NTSTATUS smb2_signing_key_copy(TALLOC_CTX *mem_ctx, + const struct smb2_signing_key *src, + struct smb2_signing_key **_dst) +{ + struct smb2_signing_key *dst = NULL; + + dst = talloc_zero(mem_ctx, struct smb2_signing_key); + if (dst == NULL) { + return NT_STATUS_NO_MEMORY; + } + talloc_set_destructor(dst, smb2_signing_key_destructor); + + dst->sign_algo_id = src->sign_algo_id; + dst->cipher_algo_id = src->cipher_algo_id; + + if (src->blob.length == 0) { + *_dst = dst; + return NT_STATUS_OK; + } + + dst->blob = data_blob_talloc_zero(dst, src->blob.length); + if (dst->blob.length == 0) { + TALLOC_FREE(dst); + return NT_STATUS_NO_MEMORY; + } + talloc_keep_secret(dst->blob.data); + memcpy(dst->blob.data, src->blob.data, dst->blob.length); + + *_dst = dst; + return NT_STATUS_OK; +} + +static NTSTATUS smb2_signing_key_create(TALLOC_CTX *mem_ctx, + uint16_t sign_algo_id, + uint16_t cipher_algo_id, + const DATA_BLOB *master_key, + const struct smb2_signing_derivation *d, + struct smb2_signing_key **_key) +{ + struct smb2_signing_key *key = NULL; + size_t in_key_length = 16; + size_t out_key_length = 16; + NTSTATUS status; + + if (sign_algo_id != SMB2_SIGNING_INVALID_ALGO) { + SMB_ASSERT(cipher_algo_id == SMB2_ENCRYPTION_INVALID_ALGO); + } + if (cipher_algo_id != SMB2_ENCRYPTION_INVALID_ALGO) { + SMB_ASSERT(sign_algo_id == SMB2_SIGNING_INVALID_ALGO); + } + + key = talloc_zero(mem_ctx, struct smb2_signing_key); + if (key == NULL) { + return NT_STATUS_NO_MEMORY; + } + talloc_set_destructor(key, smb2_signing_key_destructor); + + key->sign_algo_id = sign_algo_id; + key->cipher_algo_id = cipher_algo_id; + + if (master_key == NULL) { + SMB_ASSERT(d == NULL); + + *_key = key; + return NT_STATUS_OK; + } + + /* + * Per default use the full key. + */ + in_key_length = out_key_length = master_key->length; + switch (sign_algo_id) { + case SMB2_SIGNING_INVALID_ALGO: + /* + * This means we're processing cipher_algo_id below + */ + break; + case SMB2_SIGNING_MD5_SMB1: + SMB_ASSERT(d == NULL); + break; + case SMB2_SIGNING_HMAC_SHA256: + case SMB2_SIGNING_AES128_CMAC: + case SMB2_SIGNING_AES128_GMAC: + /* + * signing keys are padded or truncated to + * 16 bytes. + * + * Even with master_key->length = 0, + * we need to use 16 zeros. + */ + in_key_length = out_key_length = 16; + break; + default: + DBG_ERR("sign_algo_id[%u] not supported\n", sign_algo_id); + return NT_STATUS_HMAC_NOT_SUPPORTED; + } + switch (cipher_algo_id) { + case SMB2_ENCRYPTION_INVALID_ALGO: + /* + * This means we're processing sign_algo_id above + */ + break; + case SMB2_ENCRYPTION_NONE: + /* + * No encryption negotiated. + */ + break; + case SMB2_ENCRYPTION_AES128_CCM: + case SMB2_ENCRYPTION_AES128_GCM: + /* + * encryption keys are padded or truncated to + * 16 bytes. + */ + if (master_key->length == 0) { + DBG_ERR("cipher_algo_id[%u] without key\n", + cipher_algo_id); + return NT_STATUS_NO_USER_SESSION_KEY; + } + in_key_length = out_key_length = 16; + break; + case SMB2_ENCRYPTION_AES256_CCM: + case SMB2_ENCRYPTION_AES256_GCM: + /* + * AES256 uses the available input and + * generated a 32 byte encryption key. + */ + if (master_key->length == 0) { + DBG_ERR("cipher_algo_id[%u] without key\n", + cipher_algo_id); + return NT_STATUS_NO_USER_SESSION_KEY; + } + out_key_length = 32; + break; + default: + DBG_ERR("cipher_algo_id[%u] not supported\n", cipher_algo_id); + return NT_STATUS_FWP_INCOMPATIBLE_CIPHER_CONFIG; + } + + if (out_key_length == 0) { + *_key = key; + return NT_STATUS_OK; + } + + key->blob = data_blob_talloc_zero(key, out_key_length); + if (key->blob.length == 0) { + TALLOC_FREE(key); + return NT_STATUS_NO_MEMORY; + } + talloc_keep_secret(key->blob.data); + memcpy(key->blob.data, + master_key->data, + MIN(key->blob.length, master_key->length)); + + if (d == NULL) { + *_key = key; + return NT_STATUS_OK; + } + + status = smb2_key_derivation(key->blob.data, in_key_length, + d->label.data, d->label.length, + d->context.data, d->context.length, + key->blob.data, out_key_length); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(key); + return status; + } + + *_key = key; + return NT_STATUS_OK; +} + +NTSTATUS smb2_signing_key_sign_create(TALLOC_CTX *mem_ctx, + uint16_t sign_algo_id, + const DATA_BLOB *master_key, + const struct smb2_signing_derivation *d, + struct smb2_signing_key **_key) +{ + return smb2_signing_key_create(mem_ctx, + sign_algo_id, + SMB2_ENCRYPTION_INVALID_ALGO, + master_key, + d, + _key); +} + +NTSTATUS smb2_signing_key_cipher_create(TALLOC_CTX *mem_ctx, + uint16_t cipher_algo_id, + const DATA_BLOB *master_key, + const struct smb2_signing_derivation *d, + struct smb2_signing_key **_key) +{ + return smb2_signing_key_create(mem_ctx, + SMB2_SIGNING_INVALID_ALGO, + cipher_algo_id, + master_key, + d, + _key); +} + +bool smb2_signing_key_valid(const struct smb2_signing_key *key) +{ + if (key == NULL) { + return false; + } + + if (key->blob.length == 0 || key->blob.data == NULL) { + return false; + } + + return true; +} + +static NTSTATUS smb2_signing_gmac(gnutls_aead_cipher_hd_t cipher_hnd, + const uint8_t *iv, size_t iv_size, + const giovec_t *auth_iov, uint8_t auth_iovcnt, + uint8_t *tag, size_t _tag_size) +{ + size_t tag_size = _tag_size; + int rc; +#ifdef ALLOW_GNUTLS_AEAD_CIPHER_ENCRYPTV2_AES_GCM + + rc = gnutls_aead_cipher_encryptv2(cipher_hnd, + iv, iv_size, + auth_iov, auth_iovcnt, + NULL, 0, + tag, &tag_size); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + + return NT_STATUS_OK; +#else /* ALLOW_GNUTLS_AEAD_CIPHER_ENCRYPTV2_AES_GCM */ + TALLOC_CTX *tmp_ctx = NULL; + size_t atext_size = 0; + uint8_t *atext = NULL; + size_t len = 0; + size_t i; + + /* + * If we come from python bindings, we don't have a stackframe + * around, so use the NULL context. + * + * This is fine as we make sure we free the memory. + */ + if (talloc_stackframe_exists()) { + tmp_ctx = talloc_tos(); + } + + for (i=0; i < auth_iovcnt; i++) { + atext_size += auth_iov[i].iov_len; + } + + atext = talloc_size(tmp_ctx, atext_size); + if (atext == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < auth_iovcnt; i++) { + memcpy(atext + len, + auth_iov[i].iov_base, + auth_iov[i].iov_len); + + len += auth_iov[i].iov_len; + if (len > atext_size) { + TALLOC_FREE(atext); + return NT_STATUS_INTERNAL_ERROR; + } + } + + rc = gnutls_aead_cipher_encrypt(cipher_hnd, + iv, iv_size, + atext, + atext_size, + tag_size, + NULL, 0, + tag, &tag_size); + TALLOC_FREE(atext); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + + return NT_STATUS_OK; +#endif /* ALLOW_GNUTLS_AEAD_CIPHER_ENCRYPTV2_AES_GCM */ +} + +static NTSTATUS smb2_signing_calc_signature(struct smb2_signing_key *signing_key, + uint16_t sign_algo_id, + const struct iovec *vector, + int count, + uint8_t signature[16]) +{ + const uint8_t *hdr = (uint8_t *)vector[0].iov_base; + uint16_t opcode; + uint32_t flags; + uint64_t msg_id; + static const uint8_t zero_sig[16] = { 0, }; + gnutls_mac_algorithm_t hmac_algo = GNUTLS_MAC_UNKNOWN; + int i; + + /* + * We expect + * - SMB2 HDR + * - SMB2 BODY FIXED + * - (optional) SMB2 BODY DYN + * - (optional) PADDING + */ + SMB_ASSERT(count >= 2); + SMB_ASSERT(vector[0].iov_len == SMB2_HDR_BODY); + SMB_ASSERT(count <= 4); + + opcode = SVAL(hdr, SMB2_HDR_OPCODE); + flags = IVAL(hdr, SMB2_HDR_FLAGS); + if (flags & SMB2_HDR_FLAG_REDIRECT) { + NTSTATUS pdu_status = NT_STATUS(IVAL(hdr, SMB2_HDR_STATUS)); + if (NT_STATUS_EQUAL(pdu_status, NT_STATUS_PENDING)) { + DBG_ERR("opcode[%u] NT_STATUS_PENDING\n", opcode); + return NT_STATUS_INTERNAL_ERROR; + } + if (opcode == SMB2_OP_CANCEL) { + DBG_ERR("SMB2_OP_CANCEL response should not be signed\n"); + return NT_STATUS_INTERNAL_ERROR; + } + } + msg_id = BVAL(hdr, SMB2_HDR_MESSAGE_ID); + if (msg_id == 0) { + if (opcode != SMB2_OP_CANCEL || + sign_algo_id >= SMB2_SIGNING_AES128_GMAC) + { + DBG_ERR("opcode[%u] msg_id == 0\n", opcode); + return NT_STATUS_INTERNAL_ERROR; + } + /* + * Legacy algorithms allow MID 0 + * for cancel requests + */ + } + if (msg_id == UINT64_MAX) { + DBG_ERR("opcode[%u] msg_id == UINT64_MAX\n", opcode); + return NT_STATUS_INTERNAL_ERROR; + } + + switch (sign_algo_id) { + case SMB2_SIGNING_AES128_GMAC: { + gnutls_cipher_algorithm_t algo = GNUTLS_CIPHER_AES_128_GCM; + uint32_t key_size = gnutls_cipher_get_key_size(algo); + uint32_t iv_size = gnutls_cipher_get_iv_size(algo); + size_t tag_size = gnutls_cipher_get_tag_size(algo); + gnutls_datum_t key = { + .data = signing_key->blob.data, + .size = MIN(signing_key->blob.length, key_size), + }; + uint64_t high_bits = 0; + uint8_t iv[AES_BLOCK_SIZE] = {0}; + giovec_t auth_iov[count+1]; + size_t auth_iovcnt = 0; + NTSTATUS status; + int rc; + + high_bits = flags & SMB2_HDR_FLAG_REDIRECT; + if (opcode == SMB2_OP_CANCEL) { + high_bits |= SMB2_HDR_FLAG_ASYNC; + } + SBVAL(iv, 0, msg_id); + SBVAL(iv, 8, high_bits); + + if (signing_key->cipher_hnd == NULL) { + rc = gnutls_aead_cipher_init(&signing_key->cipher_hnd, + algo, + &key); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_HMAC_NOT_SUPPORTED); + } + } + + SMB_ASSERT(key_size == 16); + SMB_ASSERT(iv_size == 12); + SMB_ASSERT(tag_size == 16); + + auth_iov[auth_iovcnt++] = (giovec_t) { + .iov_base = discard_const_p(uint8_t, hdr), + .iov_len = SMB2_HDR_SIGNATURE, + }; + auth_iov[auth_iovcnt++] = (giovec_t) { + .iov_base = discard_const_p(uint8_t, zero_sig), + .iov_len = 16, + }; + for (i=1; i < count; i++) { + auth_iov[auth_iovcnt++] = (giovec_t) { + .iov_base = discard_const_p(uint8_t, vector[i].iov_base), + .iov_len = vector[i].iov_len, + }; + } + + status = smb2_signing_gmac(signing_key->cipher_hnd, + iv, + iv_size, + auth_iov, + auth_iovcnt, + signature, + tag_size); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; + } break; + + case SMB2_SIGNING_AES128_CMAC: +#ifdef HAVE_GNUTLS_AES_CMAC + hmac_algo = GNUTLS_MAC_AES_CMAC_128; + break; +#else /* NOT HAVE_GNUTLS_AES_CMAC */ + { + struct aes_cmac_128_context ctx; + uint8_t key[AES_BLOCK_SIZE] = {0}; + + memcpy(key, + signing_key->blob.data, + MIN(signing_key->blob.length, 16)); + + aes_cmac_128_init(&ctx, key); + aes_cmac_128_update(&ctx, hdr, SMB2_HDR_SIGNATURE); + aes_cmac_128_update(&ctx, zero_sig, 16); + for (i=1; i < count; i++) { + aes_cmac_128_update(&ctx, + (const uint8_t *)vector[i].iov_base, + vector[i].iov_len); + } + aes_cmac_128_final(&ctx, signature); + + ZERO_ARRAY(key); + + return NT_STATUS_OK; + } break; +#endif + case SMB2_SIGNING_HMAC_SHA256: + hmac_algo = GNUTLS_MAC_SHA256; + break; + + default: + return NT_STATUS_HMAC_NOT_SUPPORTED; + } + + if (hmac_algo != GNUTLS_MAC_UNKNOWN) { + uint8_t digest[gnutls_hash_get_len(hmac_algo)]; + gnutls_datum_t key = { + .data = signing_key->blob.data, + .size = MIN(signing_key->blob.length, 16), + }; + int rc; + + if (signing_key->hmac_hnd == NULL) { + rc = gnutls_hmac_init(&signing_key->hmac_hnd, + hmac_algo, + key.data, + key.size); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_HMAC_NOT_SUPPORTED); + } + } + + rc = gnutls_hmac(signing_key->hmac_hnd, hdr, SMB2_HDR_SIGNATURE); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_HMAC_NOT_SUPPORTED); + } + rc = gnutls_hmac(signing_key->hmac_hnd, zero_sig, 16); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_HMAC_NOT_SUPPORTED); + } + + for (i = 1; i < count; i++) { + rc = gnutls_hmac(signing_key->hmac_hnd, + vector[i].iov_base, + vector[i].iov_len); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_HMAC_NOT_SUPPORTED); + } + } + gnutls_hmac_output(signing_key->hmac_hnd, digest); + memcpy(signature, digest, 16); + ZERO_ARRAY(digest); + return NT_STATUS_OK; + } + + return NT_STATUS_HMAC_NOT_SUPPORTED; +} + +NTSTATUS smb2_signing_sign_pdu(struct smb2_signing_key *signing_key, + struct iovec *vector, + int count) +{ + uint16_t sign_algo_id; + uint8_t *hdr; + uint64_t session_id; + uint8_t res[16]; + NTSTATUS status; + + /* + * We expect + * - SMB2 HDR + * - SMB2 BODY FIXED + * - (optional) SMB2 BODY DYN + * - (optional) PADDING + */ + SMB_ASSERT(count >= 2); + SMB_ASSERT(vector[0].iov_len == SMB2_HDR_BODY); + SMB_ASSERT(count <= 4); + + hdr = (uint8_t *)vector[0].iov_base; + + session_id = BVAL(hdr, SMB2_HDR_SESSION_ID); + if (session_id == 0) { + /* + * do not sign messages with a zero session_id. + * See MS-SMB2 3.2.4.1.1 + */ + return NT_STATUS_OK; + } + + if (!smb2_signing_key_valid(signing_key)) { + DBG_WARNING("No signing key for SMB2 signing\n"); + return NT_STATUS_ACCESS_DENIED; + } + + memset(hdr + SMB2_HDR_SIGNATURE, 0, 16); + + SIVAL(hdr, SMB2_HDR_FLAGS, IVAL(hdr, SMB2_HDR_FLAGS) | SMB2_HDR_FLAG_SIGNED); + + sign_algo_id = signing_key->sign_algo_id; + + status = smb2_signing_calc_signature(signing_key, + sign_algo_id, + vector, + count, + res); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("smb2_signing_calc_signature(sign_algo_id=%u) - %s\n", + (unsigned)sign_algo_id, nt_errstr(status)); + if (NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)) { + smb_panic(__location__); + } + return status; + } + + DEBUG(5,("signed SMB2 message (sign_algo_id=%u)\n", + (unsigned)sign_algo_id)); + + memcpy(hdr + SMB2_HDR_SIGNATURE, res, 16); + + return NT_STATUS_OK; +} + +NTSTATUS smb2_signing_check_pdu(struct smb2_signing_key *signing_key, + const struct iovec *vector, + int count) +{ + uint16_t sign_algo_id; + const uint8_t *hdr; + const uint8_t *sig; + uint64_t session_id; + uint8_t res[16]; + NTSTATUS status; + + /* + * We expect + * - SMB2 HDR + * - SMB2 BODY FIXED + * - (optional) SMB2 BODY DYN + * - (optional) PADDING + */ + SMB_ASSERT(count >= 2); + SMB_ASSERT(vector[0].iov_len == SMB2_HDR_BODY); + SMB_ASSERT(count <= 4); + + hdr = (const uint8_t *)vector[0].iov_base; + + session_id = BVAL(hdr, SMB2_HDR_SESSION_ID); + if (session_id == 0) { + /* + * do not sign messages with a zero session_id. + * See MS-SMB2 3.2.4.1.1 + */ + return NT_STATUS_OK; + } + + if (!smb2_signing_key_valid(signing_key)) { + /* we don't have the session key yet */ + return NT_STATUS_OK; + } + + sig = hdr+SMB2_HDR_SIGNATURE; + + sign_algo_id = signing_key->sign_algo_id; + + status = smb2_signing_calc_signature(signing_key, + sign_algo_id, + vector, + count, + res); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("smb2_signing_calc_signature(sign_algo_id=%u) - %s\n", + (unsigned)sign_algo_id, nt_errstr(status)); + if (NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)) { + status = NT_STATUS_ACCESS_DENIED; + } + return status; + } + + if (!mem_equal_const_time(res, sig, 16)) { + DEBUG(0,("Bad SMB2 (sign_algo_id=%u) signature for message\n", + (unsigned)sign_algo_id)); + dump_data(0, sig, 16); + dump_data(0, res, 16); + return NT_STATUS_ACCESS_DENIED; + } + + return NT_STATUS_OK; +} + +NTSTATUS smb2_key_derivation(const uint8_t *KI, size_t KI_len, + const uint8_t *Label, size_t Label_len, + const uint8_t *Context, size_t Context_len, + uint8_t *KO, size_t KO_len) +{ + gnutls_hmac_hd_t hmac_hnd = NULL; + uint8_t buf[4]; + static const uint8_t zero = 0; + const size_t digest_len = gnutls_hash_get_len(GNUTLS_DIG_SHA256); + uint8_t digest[digest_len]; + uint32_t i = 1; + uint32_t L = KO_len * 8; + int rc; + + if (KO_len > digest_len) { + DBG_ERR("KO_len[%zu] > digest_len[%zu]\n", KO_len, digest_len); + return NT_STATUS_INTERNAL_ERROR; + } + + switch (KO_len) { + case 16: + case 32: + break; + default: + DBG_ERR("KO_len[%zu] not supported\n", KO_len); + return NT_STATUS_INTERNAL_ERROR; + } + + /* + * a simplified version of + * "NIST Special Publication 800-108" section 5.1 + * using hmac-sha256. + */ + rc = gnutls_hmac_init(&hmac_hnd, + GNUTLS_MAC_SHA256, + KI, + KI_len); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_HMAC_NOT_SUPPORTED); + } + + RSIVAL(buf, 0, i); + rc = gnutls_hmac(hmac_hnd, buf, sizeof(buf)); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_HMAC_NOT_SUPPORTED); + } + rc = gnutls_hmac(hmac_hnd, Label, Label_len); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, + NT_STATUS_HMAC_NOT_SUPPORTED); + } + rc = gnutls_hmac(hmac_hnd, &zero, 1); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, + NT_STATUS_HMAC_NOT_SUPPORTED); + } + rc = gnutls_hmac(hmac_hnd, Context, Context_len); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, + NT_STATUS_HMAC_NOT_SUPPORTED); + } + RSIVAL(buf, 0, L); + rc = gnutls_hmac(hmac_hnd, buf, sizeof(buf)); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, + NT_STATUS_HMAC_NOT_SUPPORTED); + } + + gnutls_hmac_deinit(hmac_hnd, digest); + + memcpy(KO, digest, KO_len); + + ZERO_ARRAY(digest); + + return NT_STATUS_OK; +} + +NTSTATUS smb2_signing_encrypt_pdu(struct smb2_signing_key *encryption_key, + struct iovec *vector, + int count) +{ +#ifdef HAVE_GNUTLS_AEAD_CIPHER_ENCRYPTV2 + bool use_encryptv2 = false; +#endif + uint16_t cipher_id; + uint8_t *tf; + size_t a_total; + ssize_t m_total; + uint32_t iv_size = 0; + uint32_t key_size = 0; + size_t tag_size = 0; + gnutls_cipher_algorithm_t algo = 0; + gnutls_datum_t key; + gnutls_datum_t iv; + NTSTATUS status; + int rc; + + if (count < 1) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (vector[0].iov_len != SMB2_TF_HDR_SIZE) { + return NT_STATUS_INVALID_PARAMETER; + } + + tf = (uint8_t *)vector[0].iov_base; + + if (!smb2_signing_key_valid(encryption_key)) { + DBG_WARNING("No encryption key for SMB2 signing\n"); + return NT_STATUS_ACCESS_DENIED; + } + cipher_id = encryption_key->cipher_algo_id; + + a_total = SMB2_TF_HDR_SIZE - SMB2_TF_NONCE; + + m_total = iov_buflen(&vector[1], count-1); + if (m_total == -1) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + + SSVAL(tf, SMB2_TF_FLAGS, SMB2_TF_FLAGS_ENCRYPTED); + SIVAL(tf, SMB2_TF_MSG_SIZE, m_total); + + switch (cipher_id) { + case SMB2_ENCRYPTION_AES128_CCM: + algo = GNUTLS_CIPHER_AES_128_CCM; + iv_size = SMB2_AES_128_CCM_NONCE_SIZE; +#ifdef ALLOW_GNUTLS_AEAD_CIPHER_ENCRYPTV2_AES_CCM + use_encryptv2 = true; +#endif + break; + case SMB2_ENCRYPTION_AES128_GCM: + algo = GNUTLS_CIPHER_AES_128_GCM; + iv_size = gnutls_cipher_get_iv_size(algo); +#ifdef ALLOW_GNUTLS_AEAD_CIPHER_ENCRYPTV2_AES_GCM + use_encryptv2 = true; +#endif + break; + case SMB2_ENCRYPTION_AES256_CCM: + algo = GNUTLS_CIPHER_AES_256_CCM; + iv_size = SMB2_AES_128_CCM_NONCE_SIZE; +#ifdef ALLOW_GNUTLS_AEAD_CIPHER_ENCRYPTV2_AES_CCM + use_encryptv2 = true; +#endif + break; + case SMB2_ENCRYPTION_AES256_GCM: + algo = GNUTLS_CIPHER_AES_256_GCM; + iv_size = gnutls_cipher_get_iv_size(algo); +#ifdef ALLOW_GNUTLS_AEAD_CIPHER_ENCRYPTV2_AES_GCM + use_encryptv2 = true; +#endif + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + + key_size = gnutls_cipher_get_key_size(algo); + tag_size = gnutls_cipher_get_tag_size(algo); + + if (key_size != encryption_key->blob.length) { + return NT_STATUS_INTERNAL_ERROR; + } + + if (tag_size != 16) { + return NT_STATUS_INTERNAL_ERROR; + } + + key = (gnutls_datum_t) { + .data = encryption_key->blob.data, + .size = key_size, + }; + + iv = (gnutls_datum_t) { + .data = tf + SMB2_TF_NONCE, + .size = iv_size, + }; + + if (encryption_key->cipher_hnd == NULL) { + rc = gnutls_aead_cipher_init(&encryption_key->cipher_hnd, + algo, + &key); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_INTERNAL_ERROR); + goto out; + } + } + + memset(tf + SMB2_TF_NONCE + iv_size, + 0, + 16 - iv_size); + +#ifdef HAVE_GNUTLS_AEAD_CIPHER_ENCRYPTV2 + if (use_encryptv2) { + uint8_t tag[tag_size]; + giovec_t auth_iov[1]; + + auth_iov[0] = (giovec_t) { + .iov_base = tf + SMB2_TF_NONCE, + .iov_len = a_total, + }; + + rc = gnutls_aead_cipher_encryptv2(encryption_key->cipher_hnd, + iv.data, + iv.size, + auth_iov, + 1, + &vector[1], + count - 1, + tag, + &tag_size); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_INTERNAL_ERROR); + goto out; + } + + memcpy(tf + SMB2_TF_SIGNATURE, tag, tag_size); + } else +#endif /* HAVE_GNUTLS_AEAD_CIPHER_ENCRYPTV2 */ + { + size_t ptext_size = m_total; + uint8_t *ptext = NULL; + size_t ctext_size = m_total + tag_size; + uint8_t *ctext = NULL; + size_t len = 0; + int i; + TALLOC_CTX *tmp_ctx = NULL; + + /* + * If we come from python bindings, we don't have a stackframe + * around, so use the NULL context. + * + * This is fine as we make sure we free the memory. + */ + if (talloc_stackframe_exists()) { + tmp_ctx = talloc_tos(); + } + + ptext = talloc_size(tmp_ctx, ptext_size); + if (ptext == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + ctext = talloc_size(tmp_ctx, ctext_size); + if (ctext == NULL) { + TALLOC_FREE(ptext); + status = NT_STATUS_NO_MEMORY; + goto out; + } + + for (i = 1; i < count; i++) { + if (vector[i].iov_base != NULL) { + memcpy(ptext + len, + vector[i].iov_base, + vector[i].iov_len); + } + + len += vector[i].iov_len; + if (len > ptext_size) { + TALLOC_FREE(ptext); + TALLOC_FREE(ctext); + status = NT_STATUS_INTERNAL_ERROR; + goto out; + } + } + + rc = gnutls_aead_cipher_encrypt(encryption_key->cipher_hnd, + iv.data, + iv.size, + tf + SMB2_TF_NONCE, + a_total, + tag_size, + ptext, + ptext_size, + ctext, + &ctext_size); + if (rc < 0 || ctext_size != m_total + tag_size) { + TALLOC_FREE(ptext); + TALLOC_FREE(ctext); + status = gnutls_error_to_ntstatus(rc, NT_STATUS_INTERNAL_ERROR); + goto out; + } + + len = 0; + for (i = 1; i < count; i++) { + if (vector[i].iov_base != NULL) { + memcpy(vector[i].iov_base, + ctext + len, + vector[i].iov_len); + } + + len += vector[i].iov_len; + } + + memcpy(tf + SMB2_TF_SIGNATURE, ctext + m_total, tag_size); + + TALLOC_FREE(ptext); + TALLOC_FREE(ctext); + } + + DBG_INFO("Encrypted SMB2 message\n"); + + status = NT_STATUS_OK; +out: + return status; +} + +NTSTATUS smb2_signing_decrypt_pdu(struct smb2_signing_key *decryption_key, + struct iovec *vector, + int count) +{ +#ifdef HAVE_GNUTLS_AEAD_CIPHER_ENCRYPTV2 + bool use_encryptv2 = false; +#endif + uint16_t cipher_id; + uint8_t *tf; + uint16_t flags; + size_t a_total; + ssize_t m_total; + uint32_t msg_size = 0; + uint32_t iv_size = 0; + uint32_t key_size = 0; + size_t tag_size = 0; + gnutls_cipher_algorithm_t algo = 0; + gnutls_datum_t key; + gnutls_datum_t iv; + NTSTATUS status; + int rc; + + if (count < 1) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (vector[0].iov_len != SMB2_TF_HDR_SIZE) { + return NT_STATUS_INVALID_PARAMETER; + } + + tf = (uint8_t *)vector[0].iov_base; + + if (!smb2_signing_key_valid(decryption_key)) { + DBG_WARNING("No decryption key for SMB2 signing\n"); + return NT_STATUS_ACCESS_DENIED; + } + cipher_id = decryption_key->cipher_algo_id; + + a_total = SMB2_TF_HDR_SIZE - SMB2_TF_NONCE; + + m_total = iov_buflen(&vector[1], count-1); + if (m_total == -1) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + + flags = SVAL(tf, SMB2_TF_FLAGS); + msg_size = IVAL(tf, SMB2_TF_MSG_SIZE); + + if (flags != SMB2_TF_FLAGS_ENCRYPTED) { + return NT_STATUS_ACCESS_DENIED; + } + + if (msg_size != m_total) { + return NT_STATUS_INTERNAL_ERROR; + } + + switch (cipher_id) { + case SMB2_ENCRYPTION_AES128_CCM: + algo = GNUTLS_CIPHER_AES_128_CCM; + iv_size = SMB2_AES_128_CCM_NONCE_SIZE; +#ifdef ALLOW_GNUTLS_AEAD_CIPHER_ENCRYPTV2_AES_CCM + use_encryptv2 = true; +#endif + break; + case SMB2_ENCRYPTION_AES128_GCM: + algo = GNUTLS_CIPHER_AES_128_GCM; + iv_size = gnutls_cipher_get_iv_size(algo); +#ifdef ALLOW_GNUTLS_AEAD_CIPHER_ENCRYPTV2_AES_GCM + use_encryptv2 = true; +#endif + break; + case SMB2_ENCRYPTION_AES256_CCM: + algo = GNUTLS_CIPHER_AES_256_CCM; + iv_size = SMB2_AES_128_CCM_NONCE_SIZE; +#ifdef ALLOW_GNUTLS_AEAD_CIPHER_ENCRYPTV2_AES_CCM + use_encryptv2 = true; +#endif + break; + case SMB2_ENCRYPTION_AES256_GCM: + algo = GNUTLS_CIPHER_AES_256_GCM; + iv_size = gnutls_cipher_get_iv_size(algo); +#ifdef ALLOW_GNUTLS_AEAD_CIPHER_ENCRYPTV2_AES_GCM + use_encryptv2 = true; +#endif + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + + key_size = gnutls_cipher_get_key_size(algo); + tag_size = gnutls_cipher_get_tag_size(algo); + + if (key_size != decryption_key->blob.length) { + return NT_STATUS_INTERNAL_ERROR; + } + + if (tag_size != 16) { + return NT_STATUS_INTERNAL_ERROR; + } + + key = (gnutls_datum_t) { + .data = decryption_key->blob.data, + .size = key_size, + }; + + iv = (gnutls_datum_t) { + .data = tf + SMB2_TF_NONCE, + .size = iv_size, + }; + + if (decryption_key->cipher_hnd == NULL) { + rc = gnutls_aead_cipher_init(&decryption_key->cipher_hnd, + algo, + &key); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_INTERNAL_ERROR); + goto out; + } + } + +/* gnutls_aead_cipher_encryptv2() has a bug in version 3.6.10 */ +#ifdef HAVE_GNUTLS_AEAD_CIPHER_ENCRYPTV2 + if (use_encryptv2) { + giovec_t auth_iov[1]; + + auth_iov[0] = (giovec_t) { + .iov_base = tf + SMB2_TF_NONCE, + .iov_len = a_total, + }; + + rc = gnutls_aead_cipher_decryptv2(decryption_key->cipher_hnd, + iv.data, + iv.size, + auth_iov, + 1, + &vector[1], + count - 1, + tf + SMB2_TF_SIGNATURE, + tag_size); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_INTERNAL_ERROR); + goto out; + } + } else +#endif /* HAVE_GNUTLS_AEAD_CIPHER_ENCRYPTV2 */ + { + size_t ctext_size = m_total + tag_size; + uint8_t *ctext = NULL; + size_t ptext_size = m_total; + uint8_t *ptext = NULL; + size_t len = 0; + int i; + TALLOC_CTX *tmp_ctx = NULL; + + /* + * If we come from python bindings, we don't have a stackframe + * around, so use the NULL context. + * + * This is fine as we make sure we free the memory. + */ + if (talloc_stackframe_exists()) { + tmp_ctx = talloc_tos(); + } + + /* GnuTLS doesn't have a iovec API for decryption yet */ + + ptext = talloc_size(tmp_ctx, ptext_size); + if (ptext == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + ctext = talloc_size(tmp_ctx, ctext_size); + if (ctext == NULL) { + TALLOC_FREE(ptext); + status = NT_STATUS_NO_MEMORY; + goto out; + } + + + for (i = 1; i < count; i++) { + memcpy(ctext + len, + vector[i].iov_base, + vector[i].iov_len); + + len += vector[i].iov_len; + } + if (len != m_total) { + TALLOC_FREE(ptext); + TALLOC_FREE(ctext); + status = NT_STATUS_INTERNAL_ERROR; + goto out; + } + + memcpy(ctext + len, + tf + SMB2_TF_SIGNATURE, + tag_size); + + /* This function will verify the tag */ + rc = gnutls_aead_cipher_decrypt(decryption_key->cipher_hnd, + iv.data, + iv.size, + tf + SMB2_TF_NONCE, + a_total, + tag_size, + ctext, + ctext_size, + ptext, + &ptext_size); + if (rc < 0) { + TALLOC_FREE(ptext); + TALLOC_FREE(ctext); + status = gnutls_error_to_ntstatus(rc, NT_STATUS_INTERNAL_ERROR); + goto out; + } +#ifdef HAVE_GNUTLS_AEAD_CIPHER_DECRYPT_PTEXT_LEN_BUG + /* + * Note that gnutls before 3.5.2 had a bug and returned + * *ptext_len = ctext_len, instead of + * *ptext_len = ctext_len - tag_size + */ + if (ptext_size != ctext_size) { + TALLOC_FREE(ptext); + TALLOC_FREE(ctext); + rc = GNUTLS_E_SHORT_MEMORY_BUFFER; + status = gnutls_error_to_ntstatus(rc, NT_STATUS_INTERNAL_ERROR); + goto out; + } + ptext_size -= tag_size; +#endif /* HAVE_GNUTLS_AEAD_CIPHER_DECRYPT_PTEXT_LEN_BUG */ + if (ptext_size != m_total) { + TALLOC_FREE(ptext); + TALLOC_FREE(ctext); + rc = GNUTLS_E_SHORT_MEMORY_BUFFER; + status = gnutls_error_to_ntstatus(rc, NT_STATUS_INTERNAL_ERROR); + goto out; + } + + len = 0; + for (i = 1; i < count; i++) { + memcpy(vector[i].iov_base, + ptext + len, + vector[i].iov_len); + + len += vector[i].iov_len; + } + + TALLOC_FREE(ptext); + TALLOC_FREE(ctext); + } + + DBG_INFO("Decrypted SMB2 message\n"); + + status = NT_STATUS_OK; +out: + return status; +} diff --git a/libcli/smb/smb2_signing.h b/libcli/smb/smb2_signing.h new file mode 100644 index 0000000..cf6fbe5 --- /dev/null +++ b/libcli/smb/smb2_signing.h @@ -0,0 +1,100 @@ +/* + Unix SMB/CIFS implementation. + SMB2 signing + + Copyright (C) Stefan Metzmacher 2009 + + 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 _LIBCLI_SMB_SMB2_SIGNING_H_ +#define _LIBCLI_SMB_SMB2_SIGNING_H_ + +struct iovec; + +struct smb2_signing_derivation { + DATA_BLOB label; + DATA_BLOB context; +}; + +struct smb2_signing_derivations { + struct smb2_signing_derivation __signing; + const struct smb2_signing_derivation *signing; + struct smb2_signing_derivation __cipher_c2s; + const struct smb2_signing_derivation *cipher_c2s; + struct smb2_signing_derivation __cipher_s2c; + const struct smb2_signing_derivation *cipher_s2c; + struct smb2_signing_derivation __application; + const struct smb2_signing_derivation *application; +}; + +void smb2_signing_derivations_fill_const_stack(struct smb2_signing_derivations *ds, + enum protocol_types protocol, + const DATA_BLOB preauth_hash); + +struct smb2_signing_key { + DATA_BLOB blob; + uint16_t sign_algo_id; + union { +#ifdef SMB2_SIGNING_KEY_GNUTLS_TYPES + gnutls_hmac_hd_t hmac_hnd; +#endif + void *__hmac_hnd; + }; + uint16_t cipher_algo_id; + union { +#ifdef SMB2_SIGNING_KEY_GNUTLS_TYPES + gnutls_aead_cipher_hd_t cipher_hnd; +#endif + void *__cipher_hnd; + }; +}; + +NTSTATUS smb2_signing_key_copy(TALLOC_CTX *mem_ctx, + const struct smb2_signing_key *src, + struct smb2_signing_key **_dst); +NTSTATUS smb2_signing_key_sign_create(TALLOC_CTX *mem_ctx, + uint16_t sign_algo_id, + const DATA_BLOB *master_key, + const struct smb2_signing_derivation *d, + struct smb2_signing_key **_key); +NTSTATUS smb2_signing_key_cipher_create(TALLOC_CTX *mem_ctx, + uint16_t cipher_algo_id, + const DATA_BLOB *master_key, + const struct smb2_signing_derivation *d, + struct smb2_signing_key **_key); + +bool smb2_signing_key_valid(const struct smb2_signing_key *key); + +NTSTATUS smb2_signing_sign_pdu(struct smb2_signing_key *signing_key, + struct iovec *vector, + int count); + +NTSTATUS smb2_signing_check_pdu(struct smb2_signing_key *signing_key, + const struct iovec *vector, + int count); + +NTSTATUS smb2_key_derivation(const uint8_t *KI, size_t KI_len, + const uint8_t *Label, size_t Label_len, + const uint8_t *Context, size_t Context_len, + uint8_t *KO, size_t KO_len); + +NTSTATUS smb2_signing_encrypt_pdu(struct smb2_signing_key *encryption_key, + struct iovec *vector, + int count); +NTSTATUS smb2_signing_decrypt_pdu(struct smb2_signing_key *decryption_key, + struct iovec *vector, + int count); + +#endif /* _LIBCLI_SMB_SMB2_SIGNING_H_ */ diff --git a/libcli/smb/smb2cli_close.c b/libcli/smb/smb2cli_close.c new file mode 100644 index 0000000..5e31056 --- /dev/null +++ b/libcli/smb/smb2cli_close.c @@ -0,0 +1,136 @@ +/* + Unix SMB/CIFS implementation. + smb2 lib + Copyright (C) Volker Lendecke 2011 + + 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 "smb_common.h" +#include "smbXcli_base.h" + +struct smb2cli_close_state { + uint8_t fixed[24]; +}; + +static void smb2cli_close_done(struct tevent_req *subreq); + +struct tevent_req *smb2cli_close_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint16_t flags, + uint64_t fid_persistent, + uint64_t fid_volatile) +{ + struct tevent_req *req, *subreq; + struct smb2cli_close_state *state; + uint8_t *fixed; + + req = tevent_req_create(mem_ctx, &state, + struct smb2cli_close_state); + if (req == NULL) { + return NULL; + } + fixed = state->fixed; + SSVAL(fixed, 0, 24); + SSVAL(fixed, 2, flags); + SBVAL(fixed, 8, fid_persistent); + SBVAL(fixed, 16, fid_volatile); + + subreq = smb2cli_req_send(state, ev, conn, SMB2_OP_CLOSE, + 0, 0, /* flags */ + timeout_msec, + tcon, + session, + state->fixed, sizeof(state->fixed), + NULL, 0, /* dyn* */ + 0); /* max_dyn_len */ + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb2cli_close_done, req); + return req; +} + +static void smb2cli_close_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + NTSTATUS status; + static const struct smb2cli_req_expected_response expected[] = { + { + .status = NT_STATUS_OK, + .body_size = 0x3C + } + }; + + status = smb2cli_req_recv(subreq, NULL, NULL, + expected, ARRAY_SIZE(expected)); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +NTSTATUS smb2cli_close_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +NTSTATUS smb2cli_close(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint16_t flags, + uint64_t fid_persistent, + uint64_t fid_volatile) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + if (smbXcli_conn_has_async_calls(conn)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER; + goto fail; + } + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = smb2cli_close_send(frame, ev, conn, timeout_msec, + session, tcon, flags, + fid_persistent, fid_volatile); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = smb2cli_close_recv(req); + fail: + TALLOC_FREE(frame); + return status; +} diff --git a/libcli/smb/smb2cli_create.c b/libcli/smb/smb2cli_create.c new file mode 100644 index 0000000..325bc79 --- /dev/null +++ b/libcli/smb/smb2cli_create.c @@ -0,0 +1,321 @@ +/* + Unix SMB/CIFS implementation. + smb2 lib + Copyright (C) Volker Lendecke 2011 + + 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 "smb_common.h" +#include "smbXcli_base.h" +#include "smb2_create_blob.h" + +struct smb2cli_create_state { + uint8_t fixed[56]; + + uint64_t fid_persistent; + uint64_t fid_volatile; + struct smb_create_returns cr; + struct smb2_create_blobs blobs; + struct tevent_req *subreq; +}; + +static void smb2cli_create_done(struct tevent_req *subreq); +static bool smb2cli_create_cancel(struct tevent_req *req); + +struct tevent_req *smb2cli_create_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + const char *filename, + uint8_t oplock_level, /* SMB2_OPLOCK_LEVEL_* */ + uint32_t impersonation_level, /* SMB2_IMPERSONATION_* */ + uint32_t desired_access, + uint32_t file_attributes, + uint32_t share_access, + uint32_t create_disposition, + uint32_t create_options, + struct smb2_create_blobs *blobs) +{ + struct tevent_req *req, *subreq; + struct smb2cli_create_state *state; + uint8_t *fixed; + uint8_t *name_utf16; + size_t name_utf16_len; + DATA_BLOB blob; + NTSTATUS status; + size_t blobs_offset; + uint8_t *dyn; + size_t dyn_len; + size_t max_dyn_len; + uint32_t additional_flags = 0; + uint32_t clear_flags = 0; + + req = tevent_req_create(mem_ctx, &state, + struct smb2cli_create_state); + if (req == NULL) { + return NULL; + } + + if (!convert_string_talloc(state, CH_UNIX, CH_UTF16, + filename, strlen(filename), + &name_utf16, &name_utf16_len)) { + tevent_req_oom(req); + return tevent_req_post(req, ev); + } + + if (strlen(filename) == 0) { + TALLOC_FREE(name_utf16); + name_utf16_len = 0; + } + + fixed = state->fixed; + + SSVAL(fixed, 0, 57); + SCVAL(fixed, 3, oplock_level); + SIVAL(fixed, 4, impersonation_level); + SIVAL(fixed, 24, desired_access); + SIVAL(fixed, 28, file_attributes); + SIVAL(fixed, 32, share_access); + SIVAL(fixed, 36, create_disposition); + SIVAL(fixed, 40, create_options); + + SSVAL(fixed, 44, SMB2_HDR_BODY + 56); + SSVAL(fixed, 46, name_utf16_len); + + blob = data_blob_null; + + if (blobs != NULL) { + status = smb2_create_blob_push(state, &blob, *blobs); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + } + + blobs_offset = name_utf16_len; + blobs_offset = ((blobs_offset + 3) & ~3); + + if (blob.length > 0) { + blobs_offset = ((blobs_offset + 7) & ~7); + SIVAL(fixed, 48, blobs_offset + SMB2_HDR_BODY + 56); + SIVAL(fixed, 52, blob.length); + } + + dyn_len = MAX(1, blobs_offset + blob.length); + dyn = talloc_zero_array(state, uint8_t, dyn_len); + if (tevent_req_nomem(dyn, req)) { + return tevent_req_post(req, ev); + } + + if (name_utf16) { + memcpy(dyn, name_utf16, name_utf16_len); + TALLOC_FREE(name_utf16); + } + + if (blob.data != NULL) { + memcpy(dyn + blobs_offset, + blob.data, blob.length); + data_blob_free(&blob); + } + + if (smbXcli_conn_dfs_supported(conn) && + smbXcli_tcon_is_dfs_share(tcon)) + { + additional_flags |= SMB2_HDR_FLAG_DFS; + } + + /* + * We use max_dyn_len = 0 + * as we don't explicitly ask for any output length. + * + * But it's still possible for the server to return + * large create blobs. + */ + max_dyn_len = 0; + + subreq = smb2cli_req_send(state, ev, conn, SMB2_OP_CREATE, + additional_flags, clear_flags, + timeout_msec, + tcon, + session, + state->fixed, sizeof(state->fixed), + dyn, dyn_len, + max_dyn_len); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb2cli_create_done, req); + + state->subreq = subreq; + tevent_req_set_cancel_fn(req, smb2cli_create_cancel); + + return req; +} + +static bool smb2cli_create_cancel(struct tevent_req *req) +{ + struct smb2cli_create_state *state = tevent_req_data(req, + struct smb2cli_create_state); + return tevent_req_cancel(state->subreq); +} + +static void smb2cli_create_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct smb2cli_create_state *state = + tevent_req_data(req, + struct smb2cli_create_state); + NTSTATUS status; + struct iovec *iov; + uint8_t *body; + uint32_t offset, length; + static const struct smb2cli_req_expected_response expected[] = { + { + .status = NT_STATUS_OK, + .body_size = 0x59 + } + }; + + status = smb2cli_req_recv(subreq, state, &iov, + expected, ARRAY_SIZE(expected)); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + body = (uint8_t *)iov[1].iov_base; + + state->cr.oplock_level = CVAL(body, 2); + state->cr.create_action = IVAL(body, 4); + state->cr.creation_time = BVAL(body, 8); + state->cr.last_access_time = BVAL(body, 16); + state->cr.last_write_time = BVAL(body, 24); + state->cr.change_time = BVAL(body, 32); + state->cr.allocation_size = BVAL(body, 40); + state->cr.end_of_file = BVAL(body, 48); + state->cr.file_attributes = IVAL(body, 56); + state->fid_persistent = BVAL(body, 64); + state->fid_volatile = BVAL(body, 72); + + offset = IVAL(body, 80); + length = IVAL(body, 84); + + if ((offset != 0) && (length != 0)) { + if ((offset != SMB2_HDR_BODY + 88) || + (length > iov[2].iov_len)) { + tevent_req_nterror( + req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + status = smb2_create_blob_parse( + state, data_blob_const(iov[2].iov_base, length), + &state->blobs); + if (tevent_req_nterror(req, status)) { + return; + } + } + tevent_req_done(req); +} + +NTSTATUS smb2cli_create_recv(struct tevent_req *req, + uint64_t *fid_persistent, + uint64_t *fid_volatile, + struct smb_create_returns *cr, + TALLOC_CTX *mem_ctx, + struct smb2_create_blobs *blobs) +{ + struct smb2cli_create_state *state = + tevent_req_data(req, + struct smb2cli_create_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + *fid_persistent = state->fid_persistent; + *fid_volatile = state->fid_volatile; + if (cr) { + *cr = state->cr; + } + if (blobs) { + blobs->num_blobs = state->blobs.num_blobs; + blobs->blobs = talloc_move(mem_ctx, &state->blobs.blobs); + } + tevent_req_received(req); + return NT_STATUS_OK; +} + +NTSTATUS smb2cli_create(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + const char *filename, + uint8_t oplock_level, /* SMB2_OPLOCK_LEVEL_* */ + uint32_t impersonation_level, /* SMB2_IMPERSONATION_* */ + uint32_t desired_access, + uint32_t file_attributes, + uint32_t share_access, + uint32_t create_disposition, + uint32_t create_options, + struct smb2_create_blobs *blobs, + uint64_t *fid_persistent, + uint64_t *fid_volatile, + struct smb_create_returns *cr, + TALLOC_CTX *mem_ctx, + struct smb2_create_blobs *ret_blobs) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + if (smbXcli_conn_has_async_calls(conn)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER; + goto fail; + } + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = smb2cli_create_send(frame, ev, conn, timeout_msec, + session, tcon, + filename, oplock_level, + impersonation_level, desired_access, + file_attributes, share_access, + create_disposition, create_options, + blobs); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = smb2cli_create_recv(req, fid_persistent, fid_volatile, cr, + mem_ctx, ret_blobs); + fail: + TALLOC_FREE(frame); + return status; +} diff --git a/libcli/smb/smb2cli_echo.c b/libcli/smb/smb2cli_echo.c new file mode 100644 index 0000000..39c592c --- /dev/null +++ b/libcli/smb/smb2cli_echo.c @@ -0,0 +1,122 @@ +/* + Unix SMB/CIFS implementation. + smb2 lib + Copyright (C) Stefan Metzmacher 2012 + + 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 "smb_common.h" +#include "smbXcli_base.h" + +struct smb2cli_echo_state { + uint8_t fixed[0x4]; +}; + +static void smb2cli_echo_done(struct tevent_req *subreq); + +struct tevent_req *smb2cli_echo_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec) +{ + struct tevent_req *req, *subreq; + struct smb2cli_echo_state *state; + uint8_t *fixed; + + req = tevent_req_create(mem_ctx, &state, + struct smb2cli_echo_state); + if (req == NULL) { + return NULL; + } + fixed = state->fixed; + SSVAL(fixed, 0, 4); + SSVAL(fixed, 2, 0); + + subreq = smb2cli_req_send(state, ev, conn, SMB2_OP_KEEPALIVE, + 0, 0, /* flags */ + timeout_msec, + NULL, /* tcon */ + NULL, /* session */ + state->fixed, sizeof(state->fixed), + NULL, 0, /* dyn* */ + 0); /* max_dyn_len */ + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb2cli_echo_done, req); + return req; +} + +static void smb2cli_echo_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + NTSTATUS status; + static const struct smb2cli_req_expected_response expected[] = { + { + .status = NT_STATUS_OK, + .body_size = 0x04 + } + }; + + status = smb2cli_req_recv(subreq, NULL, NULL, + expected, ARRAY_SIZE(expected)); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +NTSTATUS smb2cli_echo_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +NTSTATUS smb2cli_echo(struct smbXcli_conn *conn, + uint32_t timeout_msec) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + if (smbXcli_conn_has_async_calls(conn)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER; + goto fail; + } + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = smb2cli_echo_send(frame, ev, conn, timeout_msec); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = smb2cli_echo_recv(req); + fail: + TALLOC_FREE(frame); + return status; +} diff --git a/libcli/smb/smb2cli_flush.c b/libcli/smb/smb2cli_flush.c new file mode 100644 index 0000000..f014720 --- /dev/null +++ b/libcli/smb/smb2cli_flush.c @@ -0,0 +1,133 @@ +/* + Unix SMB/CIFS implementation. + smb2 lib + Copyright (C) Volker Lendecke 2011 + + 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 "smb_common.h" +#include "smbXcli_base.h" + +struct smb2cli_flush_state { + uint8_t fixed[24]; +}; + +static void smb2cli_flush_done(struct tevent_req *subreq); + +struct tevent_req *smb2cli_flush_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint64_t fid_persistent, + uint64_t fid_volatile) +{ + struct tevent_req *req, *subreq; + struct smb2cli_flush_state *state; + uint8_t *fixed; + + req = tevent_req_create(mem_ctx, &state, + struct smb2cli_flush_state); + if (req == NULL) { + return NULL; + } + fixed = state->fixed; + SSVAL(fixed, 0, 24); + SBVAL(fixed, 8, fid_persistent); + SBVAL(fixed, 16, fid_volatile); + + subreq = smb2cli_req_send(state, ev, conn, SMB2_OP_FLUSH, + 0, 0, /* flags */ + timeout_msec, + tcon, + session, + state->fixed, sizeof(state->fixed), + NULL, 0, /* dyn* */ + 0); /* max_dyn_len */ + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb2cli_flush_done, req); + return req; +} + +static void smb2cli_flush_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + NTSTATUS status; + static const struct smb2cli_req_expected_response expected[] = { + { + .status = NT_STATUS_OK, + .body_size = 0x04 + } + }; + + status = smb2cli_req_recv(subreq, NULL, NULL, + expected, ARRAY_SIZE(expected)); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +NTSTATUS smb2cli_flush_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +NTSTATUS smb2cli_flush(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint64_t fid_persistent, + uint64_t fid_volatile) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + if (smbXcli_conn_has_async_calls(conn)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER; + goto fail; + } + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = smb2cli_flush_send(frame, ev, conn, timeout_msec, + session, tcon, + fid_persistent, fid_volatile); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = smb2cli_flush_recv(req); + fail: + TALLOC_FREE(frame); + return status; +} diff --git a/libcli/smb/smb2cli_ioctl.c b/libcli/smb/smb2cli_ioctl.c new file mode 100644 index 0000000..d638b28 --- /dev/null +++ b/libcli/smb/smb2cli_ioctl.c @@ -0,0 +1,390 @@ +/* + Unix SMB/CIFS implementation. + smb2 lib + Copyright (C) Stefan Metzmacher 2011 + + 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 "smb_common.h" +#include "smbXcli_base.h" + +struct smb2cli_ioctl_state { + uint8_t fixed[0x38]; + uint8_t dyn_pad[1]; + uint32_t max_input_length; + uint32_t max_output_length; + struct iovec *recv_iov; + bool out_valid; + DATA_BLOB out_input_buffer; + DATA_BLOB out_output_buffer; + uint32_t ctl_code; +}; + +static void smb2cli_ioctl_done(struct tevent_req *subreq); + +struct tevent_req *smb2cli_ioctl_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint64_t in_fid_persistent, + uint64_t in_fid_volatile, + uint32_t in_ctl_code, + uint32_t in_max_input_length, + const DATA_BLOB *in_input_buffer, + uint32_t in_max_output_length, + const DATA_BLOB *in_output_buffer, + uint32_t in_flags) +{ + struct tevent_req *req, *subreq; + struct smb2cli_ioctl_state *state; + uint8_t *fixed; + uint8_t *dyn; + size_t dyn_len; + uint32_t input_buffer_offset = 0; + uint32_t input_buffer_length = 0; + uint32_t output_buffer_offset = 0; + uint32_t output_buffer_length = 0; + uint32_t pad_length = 0; + uint64_t tmp64; + uint32_t max_dyn_len = 0; + + req = tevent_req_create(mem_ctx, &state, + struct smb2cli_ioctl_state); + if (req == NULL) { + return NULL; + } + state->ctl_code = in_ctl_code; + state->max_input_length = in_max_input_length; + state->max_output_length = in_max_output_length; + + tmp64 = in_max_input_length; + tmp64 += in_max_output_length; + if (tmp64 > UINT32_MAX) { + max_dyn_len = UINT32_MAX; + } else { + max_dyn_len = tmp64; + } + + if (in_input_buffer) { + input_buffer_offset = SMB2_HDR_BODY+0x38; + input_buffer_length = in_input_buffer->length; + } + + if (in_output_buffer) { + output_buffer_offset = SMB2_HDR_BODY+0x38; + output_buffer_length = in_output_buffer->length; + if (input_buffer_length > 0 && output_buffer_length > 0) { + uint32_t tmp; + output_buffer_offset += input_buffer_length; + tmp = output_buffer_offset; + output_buffer_offset = NDR_ROUND(output_buffer_offset, 8); + pad_length = output_buffer_offset - tmp; + } + } + + fixed = state->fixed; + + SSVAL(fixed, 0x00, 0x39); + SSVAL(fixed, 0x02, 0); /* reserved */ + SIVAL(fixed, 0x04, in_ctl_code); + SBVAL(fixed, 0x08, in_fid_persistent); + SBVAL(fixed, 0x10, in_fid_volatile); + SIVAL(fixed, 0x18, input_buffer_offset); + SIVAL(fixed, 0x1C, input_buffer_length); + SIVAL(fixed, 0x20, in_max_input_length); + SIVAL(fixed, 0x24, output_buffer_offset); + SIVAL(fixed, 0x28, output_buffer_length); + SIVAL(fixed, 0x2C, in_max_output_length); + SIVAL(fixed, 0x30, in_flags); + SIVAL(fixed, 0x34, 0); /* reserved */ + + if (input_buffer_length > 0 && output_buffer_length > 0) { + size_t avail = UINT32_MAX - (input_buffer_length + pad_length); + size_t ofs = output_buffer_offset - input_buffer_offset; + + if (avail < output_buffer_length) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + + dyn_len = input_buffer_length + output_buffer_length + pad_length; + + dyn = talloc_zero_array(state, uint8_t, dyn_len); + if (tevent_req_nomem(dyn, req)) { + return tevent_req_post(req, ev); + } + memcpy(dyn, in_input_buffer->data, + in_input_buffer->length); + memcpy(dyn + ofs, in_output_buffer->data, + in_output_buffer->length); + } else if (input_buffer_length > 0) { + dyn = in_input_buffer->data; + dyn_len = in_input_buffer->length; + } else if (output_buffer_length > 0) { + dyn = in_output_buffer->data; + dyn_len = in_output_buffer->length; + } else { + dyn = state->dyn_pad; + dyn_len = sizeof(state->dyn_pad); + } + + subreq = smb2cli_req_send(state, ev, conn, SMB2_OP_IOCTL, + 0, 0, /* flags */ + timeout_msec, + tcon, + session, + state->fixed, sizeof(state->fixed), + dyn, dyn_len, + max_dyn_len); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb2cli_ioctl_done, req); + return req; +} + +static void smb2cli_ioctl_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct smb2cli_ioctl_state *state = + tevent_req_data(req, + struct smb2cli_ioctl_state); + NTSTATUS status; + NTSTATUS error; + struct iovec *iov; + uint8_t *fixed; + DATA_BLOB dyn_buffer = data_blob_null; + uint32_t dyn_ofs = SMB2_HDR_BODY + 0x30; + uint32_t input_min_offset; + uint32_t input_buffer_offset; + uint32_t input_buffer_length; + uint32_t input_next_offset; + uint32_t output_min_offset; + uint32_t output_buffer_offset; + uint32_t output_buffer_length; + uint32_t output_next_offset; + static const struct smb2cli_req_expected_response expected[] = { + { + .status = NT_STATUS_OK, + .body_size = 0x31 + }, + { + .status = STATUS_BUFFER_OVERFLOW, + .body_size = 0x31 + }, + { + /* + * We need to make sure that + * a response with NT_STATUS_FILE_CLOSED + * without signing generates NT_STATUS_ACCESS_DENIED + * if the request was signed. + */ + .status = NT_STATUS_FILE_CLOSED, + .body_size = 0x09, + }, + { + /* + * a normal error + */ + .status = NT_STATUS_INVALID_PARAMETER, + .body_size = 0x09 + }, + { + /* + * a special case for FSCTL_SRV_COPYCHUNK_* + */ + .status = NT_STATUS_INVALID_PARAMETER, + .body_size = 0x31 + }, + }; + + status = smb2cli_req_recv(subreq, state, &iov, + expected, ARRAY_SIZE(expected)); + TALLOC_FREE(subreq); + if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) { + switch (state->ctl_code) { + case FSCTL_SRV_COPYCHUNK: + case FSCTL_SRV_COPYCHUNK_WRITE: + break; + default: + tevent_req_nterror(req, status); + return; + } + + if (iov[1].iov_len != 0x30) { + tevent_req_nterror(req, + NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + } else if (NT_STATUS_EQUAL(status, STATUS_BUFFER_OVERFLOW)) { + /* no error */ + } else { + if (tevent_req_nterror(req, status)) { + return; + } + } + + /* + * At this stage we're sure that got a body size of 0x31, + * either with NT_STATUS_OK, STATUS_BUFFER_OVERFLOW or + * NT_STATUS_INVALID_PARAMETER. + */ + + state->recv_iov = iov; + fixed = (uint8_t *)iov[1].iov_base; + dyn_buffer = data_blob_const((uint8_t *)iov[2].iov_base, + iov[2].iov_len); + + input_buffer_offset = IVAL(fixed, 0x18); + input_buffer_length = IVAL(fixed, 0x1C); + output_buffer_offset = IVAL(fixed, 0x20); + output_buffer_length = IVAL(fixed, 0x24); + + input_min_offset = dyn_ofs; + input_next_offset = dyn_ofs; + error = smb2cli_parse_dyn_buffer(dyn_ofs, + dyn_buffer, + input_min_offset, + input_buffer_offset, + input_buffer_length, + state->max_input_length, + &input_next_offset, + &state->out_input_buffer); + if (tevent_req_nterror(req, error)) { + return; + } + + /* + * If output data is returned, the output offset MUST be set to + * InputOffset + InputCount rounded up to a multiple of 8. + */ + output_min_offset = NDR_ROUND(input_next_offset, 8); + output_next_offset = 0; /* this variable is completely ignored */ + error = smb2cli_parse_dyn_buffer(dyn_ofs, + dyn_buffer, + output_min_offset, + output_buffer_offset, + output_buffer_length, + state->max_output_length, + &output_next_offset, + &state->out_output_buffer); + if (tevent_req_nterror(req, error)) { + return; + } + + state->out_valid = true; + + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); +} + +NTSTATUS smb2cli_ioctl_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + DATA_BLOB *out_input_buffer, + DATA_BLOB *out_output_buffer) +{ + struct smb2cli_ioctl_state *state = + tevent_req_data(req, + struct smb2cli_ioctl_state); + NTSTATUS status = NT_STATUS_OK; + + if (tevent_req_is_nterror(req, &status) && !state->out_valid) { + if (out_input_buffer) { + *out_input_buffer = data_blob_null; + } + if (out_output_buffer) { + *out_output_buffer = data_blob_null; + } + tevent_req_received(req); + return status; + } + + talloc_steal(mem_ctx, state->recv_iov); + if (out_input_buffer) { + *out_input_buffer = state->out_input_buffer; + } + if (out_output_buffer) { + *out_output_buffer = state->out_output_buffer; + } + + tevent_req_received(req); + return status; +} + +NTSTATUS smb2cli_ioctl(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint64_t in_fid_persistent, + uint64_t in_fid_volatile, + uint32_t in_ctl_code, + uint32_t in_max_input_length, + const DATA_BLOB *in_input_buffer, + uint32_t in_max_output_length, + const DATA_BLOB *in_output_buffer, + uint32_t in_flags, + TALLOC_CTX *mem_ctx, + DATA_BLOB *out_input_buffer, + DATA_BLOB *out_output_buffer) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + if (smbXcli_conn_has_async_calls(conn)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER_MIX; + goto fail; + } + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = smb2cli_ioctl_send(frame, ev, conn, timeout_msec, + session, tcon, + in_fid_persistent, + in_fid_volatile, + in_ctl_code, + in_max_input_length, + in_input_buffer, + in_max_output_length, + in_output_buffer, + in_flags); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = smb2cli_ioctl_recv(req, mem_ctx, + out_input_buffer, + out_output_buffer); + fail: + TALLOC_FREE(frame); + return status; +} diff --git a/libcli/smb/smb2cli_notify.c b/libcli/smb/smb2cli_notify.c new file mode 100644 index 0000000..9026a6b --- /dev/null +++ b/libcli/smb/smb2cli_notify.c @@ -0,0 +1,236 @@ +/* + Unix SMB/CIFS implementation. + smb2 lib + Copyright (C) Volker Lendecke 2017 + + 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 "smb_common.h" +#include "smbXcli_base.h" +#include "librpc/gen_ndr/ndr_notify.h" + +struct smb2cli_notify_state { + uint8_t fixed[32]; + + struct iovec *recv_iov; + uint8_t *data; + uint32_t data_length; + + struct tevent_req *subreq; + struct tevent_req *timeout_subreq; +}; + +static void smb2cli_notify_done(struct tevent_req *subreq); +static void smb2cli_notify_timedout(struct tevent_req *subreq); +static bool smb2cli_notify_cancel(struct tevent_req *req); + +struct tevent_req *smb2cli_notify_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint32_t output_buffer_length, + uint64_t fid_persistent, + uint64_t fid_volatile, + uint32_t completion_filter, + bool recursive) +{ + struct tevent_req *req; + struct smb2cli_notify_state *state; + uint8_t *fixed; + uint16_t watch_tree; + + req = tevent_req_create(mem_ctx, &state, + struct smb2cli_notify_state); + if (req == NULL) { + return NULL; + } + + watch_tree = recursive ? SMB2_WATCH_TREE : 0; + fixed = state->fixed; + SSVAL(fixed, 0, 32); + SSVAL(fixed, 2, watch_tree); + SIVAL(fixed, 4, output_buffer_length); + SBVAL(fixed, 8, fid_persistent); + SBVAL(fixed, 16, fid_volatile); + SIVAL(fixed, 24, completion_filter); + SIVAL(fixed, 28, 0); /* reserved */ + + state->subreq = smb2cli_req_send(state, ev, conn, SMB2_OP_NOTIFY, + 0, 0, /* flags */ + 0, /* timeout_msec */ + tcon, + session, + state->fixed, sizeof(state->fixed), + NULL, 0, /* dyn* */ + 0); /* max_dyn_len */ + if (tevent_req_nomem(state->subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(state->subreq, smb2cli_notify_done, req); + + if (timeout_msec != 0) { + state->timeout_subreq = tevent_wakeup_send( + state, ev, timeval_current_ofs_msec(timeout_msec)); + if (tevent_req_nomem(state->timeout_subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback( + state->timeout_subreq, smb2cli_notify_timedout, req); + } + + tevent_req_set_cancel_fn(req, smb2cli_notify_cancel); + + return req; +} + +static bool smb2cli_notify_cancel(struct tevent_req *req) +{ + struct smb2cli_notify_state *state = tevent_req_data( + req, struct smb2cli_notify_state); + bool ok; + + TALLOC_FREE(state->timeout_subreq); + + ok = tevent_req_cancel(state->subreq); + return ok; +} + +static void smb2cli_notify_timedout(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct smb2cli_notify_state *state = tevent_req_data( + req, struct smb2cli_notify_state); + bool ok; + + ok = tevent_wakeup_recv(subreq); + if (!ok) { + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + return; + } + + ok = tevent_req_cancel(state->subreq); + if (!ok) { + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + return; + } +} + +static void smb2cli_notify_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct smb2cli_notify_state *state = tevent_req_data( + req, struct smb2cli_notify_state); + NTSTATUS status; + struct iovec *iov; + uint16_t data_offset; + static const struct smb2cli_req_expected_response expected[] = { + { + .status = NT_STATUS_OK, + .body_size = 0x09 + } + }; + + status = smb2cli_req_recv(subreq, state, &iov, + expected, ARRAY_SIZE(expected)); + TALLOC_FREE(subreq); + + if (NT_STATUS_EQUAL(status, NT_STATUS_CANCELLED)) { + status = NT_STATUS_IO_TIMEOUT; + } + if (tevent_req_nterror(req, status)) { + return; + } + + data_offset = SVAL(iov[1].iov_base, 2); + state->data_length = IVAL(iov[1].iov_base, 4); + + if ((data_offset != SMB2_HDR_BODY + 8) || + (state->data_length > iov[2].iov_len)) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + state->recv_iov = iov; + state->data = (uint8_t *)iov[2].iov_base; + tevent_req_done(req); +} + +NTSTATUS smb2cli_notify_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + uint8_t **data, uint32_t *data_length) +{ + struct smb2cli_notify_state *state = tevent_req_data( + req, struct smb2cli_notify_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + return status; + } + talloc_steal(mem_ctx, state->recv_iov); + *data_length = state->data_length; + *data = state->data; + return NT_STATUS_OK; +} + +NTSTATUS smb2cli_notify(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint32_t output_buffer_length, + uint64_t fid_persistent, + uint64_t fid_volatile, + uint32_t completion_filter, + bool recursive, + TALLOC_CTX *mem_ctx, + uint8_t **data, + uint32_t *data_length) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + if (smbXcli_conn_has_async_calls(conn)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER; + goto fail; + } + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = smb2cli_notify_send(frame, ev, conn, timeout_msec, + session, tcon, output_buffer_length, + fid_persistent, fid_volatile, + completion_filter, recursive); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = smb2cli_notify_recv(req, mem_ctx, data, data_length); + fail: + TALLOC_FREE(frame); + return status; +} diff --git a/libcli/smb/smb2cli_query_directory.c b/libcli/smb/smb2cli_query_directory.c new file mode 100644 index 0000000..e6321ff --- /dev/null +++ b/libcli/smb/smb2cli_query_directory.c @@ -0,0 +1,210 @@ +/* + Unix SMB/CIFS implementation. + smb2 lib + Copyright (C) Volker Lendecke 2011 + + 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 "smb_common.h" +#include "smbXcli_base.h" + +struct smb2cli_query_directory_state { + uint8_t fixed[32]; + uint8_t dyn_pad[1]; + struct iovec *recv_iov; + uint8_t *data; + uint32_t data_length; +}; + +static void smb2cli_query_directory_done(struct tevent_req *subreq); + +struct tevent_req *smb2cli_query_directory_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint8_t level, + uint8_t flags, + uint32_t file_index, + uint64_t fid_persistent, + uint64_t fid_volatile, + const char *mask, + uint32_t outbuf_len) +{ + struct tevent_req *req, *subreq; + struct smb2cli_query_directory_state *state; + uint8_t *fixed; + uint8_t *dyn; + size_t dyn_len; + + req = tevent_req_create(mem_ctx, &state, + struct smb2cli_query_directory_state); + if (req == NULL) { + return NULL; + } + + if (!convert_string_talloc(state, CH_UNIX, CH_UTF16, + mask, strlen(mask), + &dyn, &dyn_len)) { + tevent_req_oom(req); + return tevent_req_post(req, ev); + } + + if (strlen(mask) == 0) { + TALLOC_FREE(dyn); + dyn_len = 0; + } + + fixed = state->fixed; + SSVAL(fixed, 0, 33); + SCVAL(fixed, 2, level); + SCVAL(fixed, 3, flags); + SIVAL(fixed, 4, file_index); + SBVAL(fixed, 8, fid_persistent); + SBVAL(fixed, 16, fid_volatile); + SSVAL(fixed, 24, SMB2_HDR_BODY + 32); + SSVAL(fixed, 26, dyn_len); + SIVAL(fixed, 28, outbuf_len); + + if (dyn_len == 0) { + dyn = state->dyn_pad; + dyn_len = sizeof(state->dyn_pad); + } + + subreq = smb2cli_req_send(state, ev, conn, SMB2_OP_QUERY_DIRECTORY, + 0, 0, /* flags */ + timeout_msec, + tcon, + session, + state->fixed, sizeof(state->fixed), + dyn, dyn_len, + outbuf_len); /* max_dyn_len */ + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb2cli_query_directory_done, req); + return req; +} + +static void smb2cli_query_directory_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct smb2cli_query_directory_state *state = + tevent_req_data(req, + struct smb2cli_query_directory_state); + NTSTATUS status; + struct iovec *iov; + uint16_t data_offset; + static const struct smb2cli_req_expected_response expected[] = { + { + .status = NT_STATUS_OK, + .body_size = 0x09 + } + }; + + status = smb2cli_req_recv(subreq, state, &iov, + expected, ARRAY_SIZE(expected)); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + data_offset = SVAL(iov[1].iov_base, 2); + state->data_length = IVAL(iov[1].iov_base, 4); + + if ((data_offset != SMB2_HDR_BODY + 8) || + (state->data_length > iov[2].iov_len)) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + state->recv_iov = iov; + state->data = (uint8_t *)iov[2].iov_base; + tevent_req_done(req); +} + +NTSTATUS smb2cli_query_directory_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uint8_t **data, + uint32_t *data_length) +{ + struct smb2cli_query_directory_state *state = + tevent_req_data(req, + struct smb2cli_query_directory_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + return status; + } + talloc_steal(mem_ctx, state->recv_iov); + *data_length = state->data_length; + *data = state->data; + return NT_STATUS_OK; +} + +NTSTATUS smb2cli_query_directory(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint8_t level, + uint8_t flags, + uint32_t file_index, + uint64_t fid_persistent, + uint64_t fid_volatile, + const char *mask, + uint32_t outbuf_len, + TALLOC_CTX *mem_ctx, + uint8_t **data, + uint32_t *data_length) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + if (smbXcli_conn_has_async_calls(conn)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER; + goto fail; + } + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = smb2cli_query_directory_send(frame, ev, conn, timeout_msec, + session, tcon, + level, flags, + file_index, fid_persistent, + fid_volatile, mask, outbuf_len); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = smb2cli_query_directory_recv(req, mem_ctx, + data, data_length); + fail: + TALLOC_FREE(frame); + return status; +} diff --git a/libcli/smb/smb2cli_query_info.c b/libcli/smb/smb2cli_query_info.c new file mode 100644 index 0000000..d499611 --- /dev/null +++ b/libcli/smb/smb2cli_query_info.c @@ -0,0 +1,266 @@ +/* + Unix SMB/CIFS implementation. + smb2 lib + Copyright (C) Stefan Metzmacher 2012 + + 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 "smb_common.h" +#include "smbXcli_base.h" + +struct smb2cli_query_info_state { + uint8_t fixed[0x28]; + uint8_t dyn_pad[1]; + uint32_t max_output_length; + struct iovec *recv_iov; + DATA_BLOB out_output_buffer; + bool out_valid; +}; + +static void smb2cli_query_info_done(struct tevent_req *subreq); + +struct tevent_req *smb2cli_query_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint8_t in_info_type, + uint8_t in_file_info_class, + uint32_t in_max_output_length, + const DATA_BLOB *in_input_buffer, + uint32_t in_additional_info, + uint32_t in_flags, + uint64_t in_fid_persistent, + uint64_t in_fid_volatile) +{ + struct tevent_req *req, *subreq; + struct smb2cli_query_info_state *state; + uint8_t *fixed; + uint8_t *dyn; + size_t dyn_len; + uint16_t input_buffer_offset = 0; + uint32_t input_buffer_length = 0; + + req = tevent_req_create(mem_ctx, &state, + struct smb2cli_query_info_state); + if (req == NULL) { + return NULL; + } + state->max_output_length = in_max_output_length; + + if (in_input_buffer) { + input_buffer_offset = SMB2_HDR_BODY+0x28; + input_buffer_length = in_input_buffer->length; + } + + fixed = state->fixed; + + SSVAL(fixed, 0x00, 0x29); + SCVAL(fixed, 0x02, in_info_type); + SCVAL(fixed, 0x03, in_file_info_class); /* reserved */ + SIVAL(fixed, 0x04, in_max_output_length); + SSVAL(fixed, 0x08, input_buffer_offset); + SSVAL(fixed, 0x0A, 0); /* reserved */ + SIVAL(fixed, 0x0C, input_buffer_length); + SIVAL(fixed, 0x10, in_additional_info); + SIVAL(fixed, 0x14, in_flags); + SBVAL(fixed, 0x18, in_fid_persistent); + SBVAL(fixed, 0x20, in_fid_volatile); + + if (input_buffer_length > 0) { + dyn = in_input_buffer->data; + dyn_len = in_input_buffer->length; + } else { + dyn = state->dyn_pad; + dyn_len = sizeof(state->dyn_pad); + } + + subreq = smb2cli_req_send(state, ev, conn, SMB2_OP_GETINFO, + 0, 0, /* flags */ + timeout_msec, + tcon, + session, + state->fixed, sizeof(state->fixed), + dyn, dyn_len, + in_max_output_length); /* max_dyn_len */ + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb2cli_query_info_done, req); + return req; +} + +static void smb2cli_query_info_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct smb2cli_query_info_state *state = + tevent_req_data(req, + struct smb2cli_query_info_state); + NTSTATUS status; + struct iovec *iov; + uint8_t *fixed; + uint8_t *dyn; + size_t dyn_len; + uint32_t dyn_ofs = SMB2_HDR_BODY + 0x08; + uint32_t output_buffer_offset; + uint32_t output_buffer_length; + static const struct smb2cli_req_expected_response expected[] = { + { + .status = NT_STATUS_OK, + .body_size = 0x09 + }, + { + .status = STATUS_BUFFER_OVERFLOW, + .body_size = 0x09 + } + }; + + status = smb2cli_req_recv(subreq, state, &iov, + expected, ARRAY_SIZE(expected)); + TALLOC_FREE(subreq); + if (NT_STATUS_EQUAL(status, STATUS_BUFFER_OVERFLOW)) { + /* no error */ + } else { + if (tevent_req_nterror(req, status)) { + return; + } + } + + state->recv_iov = iov; + fixed = (uint8_t *)iov[1].iov_base; + dyn = (uint8_t *)iov[2].iov_base; + dyn_len = iov[2].iov_len; + + output_buffer_offset = SVAL(fixed, 0x02); + output_buffer_length = IVAL(fixed, 0x04); + + if ((output_buffer_offset > 0) && (output_buffer_length > 0)) { + if (output_buffer_offset != dyn_ofs) { + tevent_req_nterror( + req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + if (output_buffer_length > dyn_len) { + tevent_req_nterror( + req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + if (output_buffer_length > state->max_output_length) { + tevent_req_nterror( + req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + state->out_output_buffer.data = dyn; + state->out_output_buffer.length = output_buffer_length; + } + + state->out_valid = true; + + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); +} + +NTSTATUS smb2cli_query_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + DATA_BLOB *out_output_buffer) +{ + struct smb2cli_query_info_state *state = + tevent_req_data(req, + struct smb2cli_query_info_state); + NTSTATUS status = NT_STATUS_OK; + + if (tevent_req_is_nterror(req, &status) && !state->out_valid) { + if (out_output_buffer) { + *out_output_buffer = data_blob_null; + } + tevent_req_received(req); + return status; + } + + talloc_steal(mem_ctx, state->recv_iov); + if (out_output_buffer) { + *out_output_buffer = state->out_output_buffer; + } + + tevent_req_received(req); + return status; +} + +NTSTATUS smb2cli_query_info(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint8_t in_info_type, + uint8_t in_file_info_class, + uint32_t in_max_output_length, + const DATA_BLOB *in_input_buffer, + uint32_t in_additional_info, + uint32_t in_flags, + uint64_t in_fid_persistent, + uint64_t in_fid_volatile, + TALLOC_CTX *mem_ctx, + DATA_BLOB *out_output_buffer) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + if (smbXcli_conn_has_async_calls(conn)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER_MIX; + goto fail; + } + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = smb2cli_query_info_send(frame, ev, + conn, timeout_msec, + session, tcon, + in_info_type, + in_file_info_class, + in_max_output_length, + in_input_buffer, + in_additional_info, + in_flags, + in_fid_persistent, + in_fid_volatile); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = smb2cli_query_info_recv(req, mem_ctx, + out_output_buffer); + fail: + TALLOC_FREE(frame); + return status; +} diff --git a/libcli/smb/smb2cli_read.c b/libcli/smb/smb2cli_read.c new file mode 100644 index 0000000..c7f4874 --- /dev/null +++ b/libcli/smb/smb2cli_read.c @@ -0,0 +1,218 @@ +/* + Unix SMB/CIFS implementation. + smb2 lib + Copyright (C) Volker Lendecke 2011 + + 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 "smb_common.h" +#include "smbXcli_base.h" + +struct smb2cli_read_state { + uint8_t fixed[48]; + uint8_t dyn_pad[1]; + struct iovec *recv_iov; + uint8_t *data; + uint32_t data_length; + bool out_valid; +}; + +static void smb2cli_read_done(struct tevent_req *subreq); + +struct tevent_req *smb2cli_read_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint32_t length, + uint64_t offset, + uint64_t fid_persistent, + uint64_t fid_volatile, + uint64_t minimum_count, + uint64_t remaining_bytes) +{ + struct tevent_req *req, *subreq; + struct smb2cli_read_state *state; + uint8_t *fixed; + + req = tevent_req_create(mem_ctx, &state, + struct smb2cli_read_state); + if (req == NULL) { + return NULL; + } + + fixed = state->fixed; + + SSVAL(fixed, 0, 49); + SIVAL(fixed, 4, length); + SBVAL(fixed, 8, offset); + SBVAL(fixed, 16, fid_persistent); + SBVAL(fixed, 24, fid_volatile); + SBVAL(fixed, 32, minimum_count); + SBVAL(fixed, 40, remaining_bytes); + + subreq = smb2cli_req_send(state, ev, conn, SMB2_OP_READ, + 0, 0, /* flags */ + timeout_msec, + tcon, + session, + state->fixed, sizeof(state->fixed), + state->dyn_pad, sizeof(state->dyn_pad), + length); /* max_dyn_len */ + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb2cli_read_done, req); + return req; +} + +static void smb2cli_read_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct smb2cli_read_state *state = + tevent_req_data(req, + struct smb2cli_read_state); + NTSTATUS status; + NTSTATUS error; + struct iovec *iov; + const uint8_t dyn_ofs = SMB2_HDR_BODY + 0x10; + DATA_BLOB dyn_buffer = data_blob_null; + uint8_t data_offset; + DATA_BLOB data_buffer = data_blob_null; + uint32_t next_offset = 0; /* this variable is completely ignored */ + static const struct smb2cli_req_expected_response expected[] = { + { + .status = STATUS_BUFFER_OVERFLOW, + .body_size = 0x11 + }, + { + .status = NT_STATUS_OK, + .body_size = 0x11 + } + }; + + status = smb2cli_req_recv(subreq, state, &iov, + expected, ARRAY_SIZE(expected)); + TALLOC_FREE(subreq); + if (NT_STATUS_EQUAL(status, STATUS_BUFFER_OVERFLOW)) { + /* no error */ + } else { + if (tevent_req_nterror(req, status)) { + return; + } + } + + data_offset = CVAL(iov[1].iov_base, 2); + state->data_length = IVAL(iov[1].iov_base, 4); + + dyn_buffer = data_blob_const((uint8_t *)iov[2].iov_base, + iov[2].iov_len); + + error = smb2cli_parse_dyn_buffer(dyn_ofs, + dyn_buffer, + dyn_ofs, /* min_offset */ + data_offset, + state->data_length, + dyn_buffer.length, /* max_length */ + &next_offset, + &data_buffer); + if (tevent_req_nterror(req, error)) { + return; + } + + state->recv_iov = iov; + state->data = data_buffer.data; + + state->out_valid = true; + + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); +} + +NTSTATUS smb2cli_read_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + uint8_t **data, uint32_t *data_length) +{ + struct smb2cli_read_state *state = + tevent_req_data(req, + struct smb2cli_read_state); + NTSTATUS status = NT_STATUS_OK; + + if (tevent_req_is_nterror(req, &status) && !state->out_valid) { + *data_length = 0; + *data = NULL; + tevent_req_received(req); + return status; + } + talloc_steal(mem_ctx, state->recv_iov); + *data_length = state->data_length; + *data = state->data; + tevent_req_received(req); + return status; +} + +NTSTATUS smb2cli_read(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint32_t length, + uint64_t offset, + uint64_t fid_persistent, + uint64_t fid_volatile, + uint64_t minimum_count, + uint64_t remaining_bytes, + TALLOC_CTX *mem_ctx, + uint8_t **data, + uint32_t *data_length) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + if (smbXcli_conn_has_async_calls(conn)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER; + goto fail; + } + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = smb2cli_read_send(frame, ev, + conn, timeout_msec, session, tcon, + length, offset, + fid_persistent, fid_volatile, + minimum_count, remaining_bytes); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = smb2cli_read_recv(req, mem_ctx, data, data_length); + fail: + TALLOC_FREE(frame); + return status; +} diff --git a/libcli/smb/smb2cli_session.c b/libcli/smb/smb2cli_session.c new file mode 100644 index 0000000..65a604a --- /dev/null +++ b/libcli/smb/smb2cli_session.c @@ -0,0 +1,350 @@ +/* + Unix SMB/CIFS implementation. + smb2 lib + Copyright (C) Volker Lendecke 2011 + + 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 "../libcli/smb/smb_common.h" +#include "../libcli/smb/smbXcli_base.h" + +struct smb2cli_session_setup_state { + struct smbXcli_session *session; + uint8_t fixed[24]; + uint8_t dyn_pad[1]; + struct iovec *recv_iov; + DATA_BLOB out_security_buffer; + NTSTATUS status; +}; + +static void smb2cli_session_setup_done(struct tevent_req *subreq); + +struct tevent_req *smb2cli_session_setup_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + uint8_t in_flags, + uint32_t in_capabilities, + uint32_t in_channel, + uint64_t in_previous_session_id, + const DATA_BLOB *in_security_buffer) +{ + struct tevent_req *req, *subreq; + struct smb2cli_session_setup_state *state; + uint8_t *buf; + uint8_t *dyn; + size_t dyn_len; + uint8_t security_mode; + uint16_t security_buffer_offset = 0; + uint16_t security_buffer_length = 0; + + req = tevent_req_create(mem_ctx, &state, + struct smb2cli_session_setup_state); + if (req == NULL) { + return NULL; + } + + if (session == NULL) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + state->session = session; + security_mode = smb2cli_session_security_mode(session); + + if (in_security_buffer) { + if (in_security_buffer->length > UINT16_MAX) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + security_buffer_offset = SMB2_HDR_BODY + 24; + security_buffer_length = in_security_buffer->length; + } + + buf = state->fixed; + + SSVAL(buf, 0, 25); + SCVAL(buf, 2, in_flags); + SCVAL(buf, 3, security_mode); + SIVAL(buf, 4, in_capabilities); + SIVAL(buf, 8, in_channel); + SSVAL(buf, 12, security_buffer_offset); + SSVAL(buf, 14, security_buffer_length); + SBVAL(buf, 16, in_previous_session_id); + + if (security_buffer_length > 0) { + dyn = in_security_buffer->data; + dyn_len = in_security_buffer->length; + } else { + dyn = state->dyn_pad;; + dyn_len = sizeof(state->dyn_pad); + } + + subreq = smb2cli_req_send(state, ev, + conn, SMB2_OP_SESSSETUP, + 0, 0, /* flags */ + timeout_msec, + NULL, /* tcon */ + session, + state->fixed, sizeof(state->fixed), + dyn, dyn_len, + UINT16_MAX); /* max_dyn_len */ + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb2cli_session_setup_done, req); + return req; +} + +static void smb2cli_session_setup_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct smb2cli_session_setup_state *state = + tevent_req_data(req, + struct smb2cli_session_setup_state); + NTSTATUS status; + NTSTATUS preauth_status; + uint64_t current_session_id; + uint64_t session_id; + uint16_t session_flags; + uint16_t expected_offset = 0; + uint16_t security_buffer_offset; + uint16_t security_buffer_length; + uint8_t *security_buffer_data = NULL; + struct iovec sent_iov[3]; + const uint8_t *hdr; + const uint8_t *body; + static const struct smb2cli_req_expected_response expected[] = { + { + .status = NT_STATUS_MORE_PROCESSING_REQUIRED, + .body_size = 0x09 + }, + { + .status = NT_STATUS_OK, + .body_size = 0x09 + } + }; + + status = smb2cli_req_recv(subreq, state, &state->recv_iov, + expected, ARRAY_SIZE(expected)); + if (!NT_STATUS_IS_OK(status) && + !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + TALLOC_FREE(subreq); + tevent_req_nterror(req, status); + return; + } + + smb2cli_req_get_sent_iov(subreq, sent_iov); + preauth_status = smb2cli_session_update_preauth(state->session, sent_iov); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, preauth_status)) { + return; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + preauth_status = smb2cli_session_update_preauth(state->session, + state->recv_iov); + if (tevent_req_nterror(req, preauth_status)) { + return; + } + } + + hdr = (const uint8_t *)state->recv_iov[0].iov_base; + body = (const uint8_t *)state->recv_iov[1].iov_base; + + session_id = BVAL(hdr, SMB2_HDR_SESSION_ID); + session_flags = SVAL(body, 2); + + security_buffer_offset = SVAL(body, 4); + security_buffer_length = SVAL(body, 6); + + if (security_buffer_length > 0) { + expected_offset = SMB2_HDR_BODY + 8; + } + if (security_buffer_offset != 0) { + security_buffer_data = (uint8_t *)state->recv_iov[2].iov_base; + expected_offset = SMB2_HDR_BODY + 8; + } + + if (security_buffer_offset != expected_offset) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + if (security_buffer_length > state->recv_iov[2].iov_len) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + state->out_security_buffer.data = security_buffer_data; + state->out_security_buffer.length = security_buffer_length; + + current_session_id = smb2cli_session_current_id(state->session); + if (current_session_id == 0) { + /* A new session was requested */ + current_session_id = session_id; + } + + if (current_session_id != session_id) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + smb2cli_session_set_id_and_flags(state->session, + session_id, session_flags); + + state->status = status; + tevent_req_done(req); +} + +NTSTATUS smb2cli_session_setup_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct iovec **recv_iov, + DATA_BLOB *out_security_buffer) +{ + struct smb2cli_session_setup_state *state = + tevent_req_data(req, + struct smb2cli_session_setup_state); + NTSTATUS status; + struct iovec *_tmp; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + if (recv_iov == NULL) { + recv_iov = &_tmp; + } + + *recv_iov = talloc_move(mem_ctx, &state->recv_iov); + + *out_security_buffer = state->out_security_buffer; + + /* + * Return the status from the server: + * NT_STATUS_MORE_PROCESSING_REQUIRED or + * NT_STATUS_OK. + */ + status = state->status; + tevent_req_received(req); + return status; +} + +struct smb2cli_logoff_state { + uint8_t fixed[4]; +}; + +static void smb2cli_logoff_done(struct tevent_req *subreq); + +struct tevent_req *smb2cli_logoff_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session) +{ + struct tevent_req *req, *subreq; + struct smb2cli_logoff_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct smb2cli_logoff_state); + if (req == NULL) { + return NULL; + } + SSVAL(state->fixed, 0, 4); + + subreq = smb2cli_req_send(state, ev, + conn, SMB2_OP_LOGOFF, + 0, 0, /* flags */ + timeout_msec, + NULL, /* tcon */ + session, + state->fixed, sizeof(state->fixed), + NULL, 0, /* dyn* */ + 0); /* max_dyn_len */ + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb2cli_logoff_done, req); + return req; +} + +static void smb2cli_logoff_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct smb2cli_logoff_state *state = + tevent_req_data(req, + struct smb2cli_logoff_state); + NTSTATUS status; + struct iovec *iov; + static const struct smb2cli_req_expected_response expected[] = { + { + .status = NT_STATUS_OK, + .body_size = 0x04 + } + }; + + status = smb2cli_req_recv(subreq, state, &iov, + expected, ARRAY_SIZE(expected)); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +NTSTATUS smb2cli_logoff_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +NTSTATUS smb2cli_logoff(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + if (smbXcli_conn_has_async_calls(conn)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER; + goto fail; + } + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = smb2cli_logoff_send(frame, ev, conn, timeout_msec, session); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = smb2cli_logoff_recv(req); + fail: + TALLOC_FREE(frame); + return status; +} diff --git a/libcli/smb/smb2cli_set_info.c b/libcli/smb/smb2cli_set_info.c new file mode 100644 index 0000000..6871370 --- /dev/null +++ b/libcli/smb/smb2cli_set_info.c @@ -0,0 +1,183 @@ +/* + Unix SMB/CIFS implementation. + smb2 lib + Copyright (C) Stefan Metzmacher 2012 + + 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 "smb_common.h" +#include "smbXcli_base.h" + +struct smb2cli_set_info_state { + uint8_t fixed[0x20]; + uint8_t dyn_pad[1]; +}; + +static void smb2cli_set_info_done(struct tevent_req *subreq); + +struct tevent_req *smb2cli_set_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint8_t in_info_type, + uint8_t in_file_info_class, + const DATA_BLOB *in_input_buffer, + uint32_t in_additional_info, + uint64_t in_fid_persistent, + uint64_t in_fid_volatile) +{ + struct tevent_req *req, *subreq; + struct smb2cli_set_info_state *state; + uint8_t *fixed; + uint8_t *dyn; + size_t dyn_len; + uint16_t input_buffer_offset = 0; + uint32_t input_buffer_length = 0; + + req = tevent_req_create(mem_ctx, &state, + struct smb2cli_set_info_state); + if (req == NULL) { + return NULL; + } + + if (in_input_buffer) { + input_buffer_offset = SMB2_HDR_BODY+0x20; + input_buffer_length = in_input_buffer->length; + } + + fixed = state->fixed; + + SSVAL(fixed, 0x00, 0x21); + SCVAL(fixed, 0x02, in_info_type); + SCVAL(fixed, 0x03, in_file_info_class); + SIVAL(fixed, 0x04, input_buffer_length); + SSVAL(fixed, 0x08, input_buffer_offset); + SSVAL(fixed, 0x0A, 0); /* reserved */ + SIVAL(fixed, 0x0C, in_additional_info); + SBVAL(fixed, 0x10, in_fid_persistent); + SBVAL(fixed, 0x18, in_fid_volatile); + + if (input_buffer_length > 0) { + dyn = in_input_buffer->data; + dyn_len = in_input_buffer->length; + } else { + dyn = state->dyn_pad; + dyn_len = sizeof(state->dyn_pad); + } + + subreq = smb2cli_req_send(state, ev, conn, SMB2_OP_SETINFO, + 0, 0, /* flags */ + timeout_msec, + tcon, + session, + state->fixed, sizeof(state->fixed), + dyn, dyn_len, + 0); /* max_dyn_len */ + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb2cli_set_info_done, req); + return req; +} + +static void smb2cli_set_info_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + NTSTATUS status; + static const struct smb2cli_req_expected_response expected[] = { + { + .status = NT_STATUS_OK, + .body_size = 0x02 + }, + }; + + status = smb2cli_req_recv(subreq, NULL, NULL, + expected, ARRAY_SIZE(expected)); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); +} + +NTSTATUS smb2cli_set_info_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; +} + +NTSTATUS smb2cli_set_info(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint8_t in_info_type, + uint8_t in_file_info_class, + const DATA_BLOB *in_input_buffer, + uint32_t in_additional_info, + uint64_t in_fid_persistent, + uint64_t in_fid_volatile) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + if (smbXcli_conn_has_async_calls(conn)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER_MIX; + goto fail; + } + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = smb2cli_set_info_send(frame, ev, + conn, timeout_msec, + session, tcon, + in_info_type, + in_file_info_class, + in_input_buffer, + in_additional_info, + in_fid_persistent, + in_fid_volatile); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = smb2cli_set_info_recv(req); + + fail: + TALLOC_FREE(frame); + return status; +} diff --git a/libcli/smb/smb2cli_tcon.c b/libcli/smb/smb2cli_tcon.c new file mode 100644 index 0000000..d5e4fc3 --- /dev/null +++ b/libcli/smb/smb2cli_tcon.c @@ -0,0 +1,461 @@ +/* + Unix SMB/CIFS implementation. + smb2 lib + Copyright (C) Volker Lendecke 2011 + + 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 "../libcli/smb/smb_common.h" +#include "../libcli/smb/smbXcli_base.h" + +struct smb2cli_raw_tcon_state { + struct smbXcli_session *session; + struct smbXcli_tcon *tcon; + uint8_t fixed[8]; + uint8_t dyn_pad[1]; +}; + +static void smb2cli_raw_tcon_done(struct tevent_req *subreq); + +struct tevent_req *smb2cli_raw_tcon_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t additional_flags, + uint32_t clear_flags, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint16_t tcon_flags, + const char *unc) +{ + struct tevent_req *req = NULL; + struct smb2cli_raw_tcon_state *state = NULL; + struct tevent_req *subreq = NULL; + uint8_t *fixed = NULL; + uint8_t *dyn = NULL; + size_t dyn_len; + + req = tevent_req_create(mem_ctx, &state, + struct smb2cli_raw_tcon_state); + if (req == NULL) { + return NULL; + } + state->session = session; + state->tcon = tcon; + + if (!convert_string_talloc(state, CH_UNIX, CH_UTF16, + unc, strlen(unc), + &dyn, &dyn_len)) { + tevent_req_oom(req); + return tevent_req_post(req, ev); + } + + if (strlen(unc) == 0) { + TALLOC_FREE(dyn); + dyn_len = 0; + } + + fixed = state->fixed; + SSVAL(fixed, 0, 9); + if (smbXcli_conn_protocol(conn) >= PROTOCOL_SMB3_11) { + SSVAL(fixed, 2, tcon_flags); + } else { + SSVAL(fixed, 2, 0); /* Reserved */ + } + SSVAL(fixed, 4, SMB2_HDR_BODY + 8); + SSVAL(fixed, 6, dyn_len); + + if (dyn_len == 0) { + dyn = state->dyn_pad; + dyn_len = sizeof(state->dyn_pad); + } + + subreq = smb2cli_req_send(state, ev, conn, SMB2_OP_TCON, + additional_flags, clear_flags, + timeout_msec, + NULL, /* tcon */ + session, + state->fixed, sizeof(state->fixed), + dyn, dyn_len, + 0); /* max_dyn_len */ + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb2cli_raw_tcon_done, req); + + return req; +} + +static void smb2cli_raw_tcon_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct smb2cli_raw_tcon_state *state = tevent_req_data( + req, struct smb2cli_raw_tcon_state); + NTSTATUS status; + struct iovec *iov; + uint8_t *body; + uint32_t tcon_id; + uint8_t share_type; + uint32_t share_flags; + uint32_t share_capabilities; + uint32_t maximal_access; + static const struct smb2cli_req_expected_response expected[] = { + { + .status = NT_STATUS_OK, + .body_size = 0x10 + } + }; + + status = smb2cli_req_recv(subreq, state, &iov, + expected, ARRAY_SIZE(expected)); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return; + } + + tcon_id = IVAL(iov[0].iov_base, SMB2_HDR_TID); + + body = (uint8_t *)iov[1].iov_base; + share_type = CVAL(body, 0x02); + share_flags = IVAL(body, 0x04); + share_capabilities = IVAL(body, 0x08); + maximal_access = IVAL(body, 0x0C); + + smb2cli_tcon_set_values(state->tcon, + state->session, + tcon_id, + share_type, + share_flags, + share_capabilities, + maximal_access); + + tevent_req_done(req); +} + +NTSTATUS smb2cli_raw_tcon_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +NTSTATUS smb2cli_raw_tcon(struct smbXcli_conn *conn, + uint32_t additional_flags, + uint32_t clear_flags, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint16_t tcon_flags, + const char *unc) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + if (smbXcli_conn_has_async_calls(conn)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER; + goto fail; + } + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = smb2cli_raw_tcon_send(frame, ev, conn, + additional_flags, clear_flags, + timeout_msec, session, tcon, + tcon_flags, unc); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = smb2cli_raw_tcon_recv(req); + fail: + TALLOC_FREE(frame); + return status; +} + +struct smb2cli_tcon_state { + struct tevent_context *ev; + struct smbXcli_conn *conn; + uint32_t timeout_msec; + struct smbXcli_session *session; + struct smbXcli_tcon *tcon; + uint8_t fixed[8]; + uint8_t dyn_pad[1]; +}; + +static void smb2cli_tcon_done(struct tevent_req *subreq); + +struct tevent_req *smb2cli_tcon_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint16_t flags, + const char *unc) +{ + struct tevent_req *req, *subreq; + struct smb2cli_tcon_state *state; + uint32_t additional_flags = 0; + uint32_t clear_flags = 0; + + req = tevent_req_create(mem_ctx, &state, struct smb2cli_tcon_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->conn = conn; + state->timeout_msec = timeout_msec; + state->session = session; + state->tcon = tcon; + + if (smbXcli_session_is_authenticated(state->session)) { + additional_flags |= SMB2_HDR_FLAG_SIGNED; + } + + subreq = smb2cli_raw_tcon_send(state, + state->ev, + state->conn, + additional_flags, + clear_flags, + state->timeout_msec, + state->session, + state->tcon, + flags, + unc); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb2cli_tcon_done, req); + + return req; +} + +static void smb2cli_tcon_validate(struct tevent_req *subreq); + +static void smb2cli_tcon_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct smb2cli_tcon_state *state = tevent_req_data( + req, struct smb2cli_tcon_state); + NTSTATUS status; + + status = smb2cli_raw_tcon_recv(subreq); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + if (!smbXcli_session_is_authenticated(state->session)) { + tevent_req_done(req); + return; + } + + if (smbXcli_conn_protocol(state->conn) >= PROTOCOL_SMB3_11) { + tevent_req_done(req); + return; + } + + subreq = smb2cli_validate_negotiate_info_send(state, state->ev, + state->conn, + state->timeout_msec, + state->session, + state->tcon); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, smb2cli_tcon_validate, req); +} + +static void smb2cli_tcon_validate(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct smb2cli_tcon_state *state = tevent_req_data( + req, struct smb2cli_tcon_state); + NTSTATUS status; + + status = smb2cli_validate_negotiate_info_recv(subreq); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + smb2cli_tcon_set_values(state->tcon, NULL, + UINT32_MAX, 0, 0, 0, 0); + tevent_req_nterror(req, status); + return; + } + + tevent_req_done(req); +} + +NTSTATUS smb2cli_tcon_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +NTSTATUS smb2cli_tcon(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint16_t flags, + const char *unc) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + if (smbXcli_conn_has_async_calls(conn)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER; + goto fail; + } + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = smb2cli_tcon_send(frame, ev, conn, + timeout_msec, session, tcon, + flags, unc); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = smb2cli_tcon_recv(req); + fail: + TALLOC_FREE(frame); + return status; +} + +struct smb2cli_tdis_state { + struct smbXcli_tcon *tcon; + uint8_t fixed[4]; +}; + +static void smb2cli_tdis_done(struct tevent_req *subreq); + +struct tevent_req *smb2cli_tdis_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon) +{ + struct tevent_req *req, *subreq; + struct smb2cli_tdis_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct smb2cli_tdis_state); + if (req == NULL) { + return NULL; + } + state->tcon = tcon; + + SSVAL(state->fixed, 0, 4); + + subreq = smb2cli_req_send(state, ev, conn, SMB2_OP_TDIS, + 0, 0, /* flags */ + timeout_msec, + tcon, session, + state->fixed, sizeof(state->fixed), + NULL, 0, /* dyn* */ + 0); /* max_dyn_len */ + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb2cli_tdis_done, req); + return req; +} + +static void smb2cli_tdis_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct smb2cli_tdis_state *state = + tevent_req_data(req, + struct smb2cli_tdis_state); + NTSTATUS status; + static const struct smb2cli_req_expected_response expected[] = { + { + .status = NT_STATUS_OK, + .body_size = 0x04 + } + }; + + status = smb2cli_req_recv(subreq, NULL, NULL, + expected, ARRAY_SIZE(expected)); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + smb2cli_tcon_set_values(state->tcon, NULL, + UINT32_MAX, 0, 0, 0, 0); + tevent_req_done(req); +} + +NTSTATUS smb2cli_tdis_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +NTSTATUS smb2cli_tdis(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + if (smbXcli_conn_has_async_calls(conn)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER; + goto fail; + } + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = smb2cli_tdis_send(frame, ev, conn, + timeout_msec, session, tcon); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = smb2cli_tdis_recv(req); + fail: + TALLOC_FREE(frame); + return status; +} diff --git a/libcli/smb/smb2cli_write.c b/libcli/smb/smb2cli_write.c new file mode 100644 index 0000000..6d0a0aa --- /dev/null +++ b/libcli/smb/smb2cli_write.c @@ -0,0 +1,183 @@ +/* + Unix SMB/CIFS implementation. + smb2 lib + Copyright (C) Volker Lendecke 2011 + + 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 "smb_common.h" +#include "smbXcli_base.h" + +struct smb2cli_write_state { + uint8_t fixed[48]; + uint8_t dyn_pad[1]; + uint32_t written; +}; + +static void smb2cli_write_done(struct tevent_req *subreq); + +struct tevent_req *smb2cli_write_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint32_t length, + uint64_t offset, + uint64_t fid_persistent, + uint64_t fid_volatile, + uint32_t remaining_bytes, + uint32_t flags, + const uint8_t *data) +{ + struct tevent_req *req, *subreq; + struct smb2cli_write_state *state; + uint8_t *fixed; + const uint8_t *dyn; + size_t dyn_len; + + req = tevent_req_create(mem_ctx, &state, + struct smb2cli_write_state); + if (req == NULL) { + return NULL; + } + + fixed = state->fixed; + + SSVAL(fixed, 0, 49); + SSVAL(fixed, 2, SMB2_HDR_BODY + 48); + SIVAL(fixed, 4, length); + SBVAL(fixed, 8, offset); + SBVAL(fixed, 16, fid_persistent); + SBVAL(fixed, 24, fid_volatile); + SIVAL(fixed, 36, remaining_bytes); + SIVAL(fixed, 44, flags); + + if (length > 0) { + dyn = data; + dyn_len = length; + } else { + dyn = state->dyn_pad;; + dyn_len = sizeof(state->dyn_pad); + } + + subreq = smb2cli_req_send(state, ev, conn, SMB2_OP_WRITE, + 0, 0, /* flags */ + timeout_msec, + tcon, + session, + state->fixed, sizeof(state->fixed), + dyn, dyn_len, + 0); /* max_dyn_len */ + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb2cli_write_done, req); + return req; +} + +static void smb2cli_write_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct smb2cli_write_state *state = + tevent_req_data(req, + struct smb2cli_write_state); + NTSTATUS status; + struct iovec *iov; + static const struct smb2cli_req_expected_response expected[] = { + { + .status = NT_STATUS_OK, + .body_size = 0x11 + } + }; + + status = smb2cli_req_recv(subreq, state, &iov, + expected, ARRAY_SIZE(expected)); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + state->written = IVAL(iov[1].iov_base, 4); + tevent_req_done(req); +} + +NTSTATUS smb2cli_write_recv(struct tevent_req *req, uint32_t *written) +{ + struct smb2cli_write_state *state = + tevent_req_data(req, + struct smb2cli_write_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + if (written) { + *written = state->written; + } + tevent_req_received(req); + return NT_STATUS_OK; +} + +NTSTATUS smb2cli_write(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint32_t length, + uint64_t offset, + uint64_t fid_persistent, + uint64_t fid_volatile, + uint32_t remaining_bytes, + uint32_t flags, + const uint8_t *data, + uint32_t *written) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + if (smbXcli_conn_has_async_calls(conn)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER; + goto fail; + } + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = smb2cli_write_send(frame, ev, conn, timeout_msec, + session, tcon, + length, offset, + fid_persistent, fid_volatile, + remaining_bytes, flags, data); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = smb2cli_write_recv(req, written); + fail: + TALLOC_FREE(frame); + return status; +} diff --git a/libcli/smb/smbXcli_base.c b/libcli/smb/smbXcli_base.c new file mode 100644 index 0000000..444963e --- /dev/null +++ b/libcli/smb/smbXcli_base.c @@ -0,0 +1,7006 @@ +/* + Unix SMB/CIFS implementation. + Infrastructure for async SMB client requests + Copyright (C) Volker Lendecke 2008 + Copyright (C) Stefan Metzmacher 2011 + + 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/async_req/async_sock.h" +#include "../lib/util/tevent_ntstatus.h" +#include "../lib/util/tevent_unix.h" +#include "lib/util/util_net.h" +#include "lib/util/dlinklist.h" +#include "lib/util/iov_buf.h" +#include "../libcli/smb/smb_common.h" +#include "../libcli/smb/smb_seal.h" +#include "../libcli/smb/smb_signing.h" +#include "../libcli/smb/read_smb.h" +#include "smbXcli_base.h" +#include "librpc/ndr/libndr.h" +#include "libcli/smb/smb2_negotiate_context.h" +#include "libcli/smb/smb2_signing.h" + +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +struct smbXcli_conn; +struct smbXcli_req; +struct smbXcli_session; +struct smbXcli_tcon; + +struct smbXcli_conn { + int sock_fd; + struct sockaddr_storage local_ss; + struct sockaddr_storage remote_ss; + const char *remote_name; + + struct tevent_queue *outgoing; + struct tevent_req **pending; + struct tevent_req *read_smb_req; + struct tevent_req *suicide_req; + + enum protocol_types min_protocol; + enum protocol_types max_protocol; + enum protocol_types protocol; + bool allow_signing; + bool desire_signing; + bool mandatory_signing; + + /* + * The incoming dispatch function should return: + * - NT_STATUS_RETRY, if more incoming PDUs are expected. + * - NT_STATUS_OK, if no more processing is desired, e.g. + * the dispatch function called + * tevent_req_done(). + * - All other return values disconnect the connection. + */ + NTSTATUS (*dispatch_incoming)(struct smbXcli_conn *conn, + TALLOC_CTX *tmp_mem, + uint8_t *inbuf); + + struct { + struct { + uint32_t capabilities; + uint32_t max_xmit; + } client; + + struct { + uint32_t capabilities; + uint32_t max_xmit; + uint16_t max_mux; + uint16_t security_mode; + bool readbraw; + bool writebraw; + bool lockread; + bool writeunlock; + uint32_t session_key; + struct GUID guid; + DATA_BLOB gss_blob; + uint8_t challenge[8]; + const char *workgroup; + const char *name; + int time_zone; + NTTIME system_time; + } server; + + uint32_t capabilities; + uint32_t max_xmit; + + uint16_t mid; + + struct smb1_signing_state *signing; + struct smb_trans_enc_state *trans_enc; + + struct tevent_req *read_braw_req; + } smb1; + + struct { + struct { + uint32_t capabilities; + uint16_t security_mode; + struct GUID guid; + struct smb311_capabilities smb3_capabilities; + } client; + + struct { + uint32_t capabilities; + uint16_t security_mode; + struct GUID guid; + uint32_t max_trans_size; + uint32_t max_read_size; + uint32_t max_write_size; + NTTIME system_time; + NTTIME start_time; + DATA_BLOB gss_blob; + uint16_t sign_algo; + uint16_t cipher; + } server; + + uint64_t mid; + uint16_t cur_credits; + uint16_t max_credits; + + uint32_t cc_chunk_len; + uint32_t cc_max_chunks; + + uint8_t io_priority; + + bool force_channel_sequence; + + uint8_t preauth_sha512[64]; + } smb2; + + struct smbXcli_session *sessions; +}; + +struct smb2cli_session { + uint64_t session_id; + uint16_t session_flags; + struct smb2_signing_key *application_key; + struct smb2_signing_key *signing_key; + bool should_sign; + bool should_encrypt; + struct smb2_signing_key *encryption_key; + struct smb2_signing_key *decryption_key; + uint64_t nonce_high_random; + uint64_t nonce_high_max; + uint64_t nonce_high; + uint64_t nonce_low; + uint16_t channel_sequence; + bool replay_active; + bool require_signed_response; +}; + +struct smbXcli_session { + struct smbXcli_session *prev, *next; + struct smbXcli_conn *conn; + + struct { + uint16_t session_id; + uint16_t action; + DATA_BLOB application_key; + bool protected_key; + } smb1; + + struct smb2cli_session *smb2; + + struct { + struct smb2_signing_key *signing_key; + uint8_t preauth_sha512[64]; + } smb2_channel; + + /* + * this should be a short term hack + * until the upper layers have implemented + * re-authentication. + */ + bool disconnect_expired; +}; + +struct smbXcli_tcon { + bool is_smb1; + uint32_t fs_attributes; + + struct { + uint16_t tcon_id; + uint16_t optional_support; + uint32_t maximal_access; + uint32_t guest_maximal_access; + char *service; + char *fs_type; + } smb1; + + struct { + uint32_t tcon_id; + uint8_t type; + uint32_t flags; + uint32_t capabilities; + uint32_t maximal_access; + bool should_sign; + bool should_encrypt; + } smb2; +}; + +struct smbXcli_req_state { + struct tevent_context *ev; + struct smbXcli_conn *conn; + struct smbXcli_session *session; /* maybe NULL */ + struct smbXcli_tcon *tcon; /* maybe NULL */ + + uint8_t length_hdr[4]; + + bool one_way; + + uint8_t *inbuf; + + struct tevent_req *write_req; + + struct timeval endtime; + + struct { + /* Space for the header including the wct */ + uint8_t hdr[HDR_VWV]; + + /* + * For normal requests, smb1cli_req_send chooses a mid. + * SecondaryV trans requests need to use the mid of the primary + * request, so we need a place to store it. + * Assume it is set if != 0. + */ + uint16_t mid; + + uint16_t *vwv; + uint8_t bytecount_buf[2]; + +#define MAX_SMB_IOV 10 + /* length_hdr, hdr, words, byte_count, buffers */ + struct iovec iov[1 + 3 + MAX_SMB_IOV]; + int iov_count; + + bool one_way_seqnum; + uint32_t seqnum; + struct tevent_req **chained_requests; + + uint8_t recv_cmd; + NTSTATUS recv_status; + /* always an array of 3 talloc elements */ + struct iovec *recv_iov; + } smb1; + + struct { + const uint8_t *fixed; + uint16_t fixed_len; + const uint8_t *dyn; + uint32_t dyn_len; + + uint8_t transform[SMB2_TF_HDR_SIZE]; + uint8_t hdr[SMB2_HDR_BODY]; + uint8_t pad[7]; /* padding space for compounding */ + + /* + * always an array of 3 talloc elements + * (without a SMB2_TRANSFORM header!) + * + * HDR, BODY, DYN + */ + struct iovec *recv_iov; + + /* + * the expected max for the response dyn_len + */ + uint32_t max_dyn_len; + + uint16_t credit_charge; + + bool should_sign; + bool should_encrypt; + uint64_t encryption_session_id; + + bool signing_skipped; + bool require_signed_response; + bool notify_async; + bool got_async; + uint16_t cancel_flags; + uint64_t cancel_mid; + uint64_t cancel_aid; + } smb2; +}; + +static int smbXcli_conn_destructor(struct smbXcli_conn *conn) +{ + /* + * NT_STATUS_OK, means we do not notify the callers + */ + smbXcli_conn_disconnect(conn, NT_STATUS_OK); + + while (conn->sessions) { + conn->sessions->conn = NULL; + DLIST_REMOVE(conn->sessions, conn->sessions); + } + + if (conn->smb1.trans_enc) { + TALLOC_FREE(conn->smb1.trans_enc); + } + + return 0; +} + +struct smbXcli_conn *smbXcli_conn_create(TALLOC_CTX *mem_ctx, + int fd, + const char *remote_name, + enum smb_signing_setting signing_state, + uint32_t smb1_capabilities, + struct GUID *client_guid, + uint32_t smb2_capabilities, + const struct smb311_capabilities *smb3_capabilities) +{ + struct smbXcli_conn *conn = NULL; + void *ss = NULL; + struct sockaddr *sa = NULL; + socklen_t sa_length; + int ret; + + if (smb3_capabilities != NULL) { + const struct smb3_signing_capabilities *sign_algos = + &smb3_capabilities->signing; + const struct smb3_encryption_capabilities *ciphers = + &smb3_capabilities->encryption; + + SMB_ASSERT(sign_algos->num_algos <= SMB3_SIGNING_CAPABILITIES_MAX_ALGOS); + SMB_ASSERT(ciphers->num_algos <= SMB3_ENCRYTION_CAPABILITIES_MAX_ALGOS); + } + + conn = talloc_zero(mem_ctx, struct smbXcli_conn); + if (!conn) { + return NULL; + } + + ret = set_blocking(fd, false); + if (ret < 0) { + goto error; + } + conn->sock_fd = fd; + + conn->remote_name = talloc_strdup(conn, remote_name); + if (conn->remote_name == NULL) { + goto error; + } + + ss = (void *)&conn->local_ss; + sa = (struct sockaddr *)ss; + sa_length = sizeof(conn->local_ss); + ret = getsockname(fd, sa, &sa_length); + if (ret == -1) { + goto error; + } + ss = (void *)&conn->remote_ss; + sa = (struct sockaddr *)ss; + sa_length = sizeof(conn->remote_ss); + ret = getpeername(fd, sa, &sa_length); + if (ret == -1) { + goto error; + } + + conn->outgoing = tevent_queue_create(conn, "smbXcli_outgoing"); + if (conn->outgoing == NULL) { + goto error; + } + conn->pending = NULL; + + conn->min_protocol = PROTOCOL_NONE; + conn->max_protocol = PROTOCOL_NONE; + conn->protocol = PROTOCOL_NONE; + + switch (signing_state) { + case SMB_SIGNING_OFF: + /* never */ + conn->allow_signing = false; + conn->desire_signing = false; + conn->mandatory_signing = false; + break; + case SMB_SIGNING_DEFAULT: + case SMB_SIGNING_IF_REQUIRED: + /* if the server requires it */ + conn->allow_signing = true; + conn->desire_signing = false; + conn->mandatory_signing = false; + break; + case SMB_SIGNING_DESIRED: + /* if the server desires it */ + conn->allow_signing = true; + conn->desire_signing = true; + conn->mandatory_signing = false; + break; + case SMB_SIGNING_IPC_DEFAULT: + case SMB_SIGNING_REQUIRED: + /* always */ + conn->allow_signing = true; + conn->desire_signing = true; + conn->mandatory_signing = true; + break; + } + + conn->smb1.client.capabilities = smb1_capabilities; + conn->smb1.client.max_xmit = UINT16_MAX; + + conn->smb1.capabilities = conn->smb1.client.capabilities; + conn->smb1.max_xmit = 1024; + + conn->smb1.mid = 1; + + /* initialise signing */ + conn->smb1.signing = smb1_signing_init(conn, + conn->allow_signing, + conn->desire_signing, + conn->mandatory_signing); + if (!conn->smb1.signing) { + goto error; + } + + conn->smb2.client.security_mode = SMB2_NEGOTIATE_SIGNING_ENABLED; + if (conn->mandatory_signing) { + conn->smb2.client.security_mode |= SMB2_NEGOTIATE_SIGNING_REQUIRED; + } + if (client_guid) { + conn->smb2.client.guid = *client_guid; + } + conn->smb2.client.capabilities = smb2_capabilities; + if (smb3_capabilities != NULL) { + conn->smb2.client.smb3_capabilities = *smb3_capabilities; + } + + conn->smb2.cur_credits = 1; + conn->smb2.max_credits = 0; + conn->smb2.io_priority = 1; + + /* + * Samba and Windows servers accept a maximum of 16 MiB with a maximum + * chunk length of 1 MiB. + */ + conn->smb2.cc_chunk_len = 1024 * 1024; + conn->smb2.cc_max_chunks = 16; + + talloc_set_destructor(conn, smbXcli_conn_destructor); + return conn; + + error: + TALLOC_FREE(conn); + return NULL; +} + +bool smbXcli_conn_is_connected(struct smbXcli_conn *conn) +{ + if (conn == NULL) { + return false; + } + + if (conn->sock_fd == -1) { + return false; + } + + return true; +} + +enum protocol_types smbXcli_conn_protocol(struct smbXcli_conn *conn) +{ + return conn->protocol; +} + +bool smbXcli_conn_use_unicode(struct smbXcli_conn *conn) +{ + if (conn->protocol >= PROTOCOL_SMB2_02) { + return true; + } + + if (conn->smb1.capabilities & CAP_UNICODE) { + return true; + } + + return false; +} + +bool smbXcli_conn_signing_mandatory(struct smbXcli_conn *conn) +{ + return conn->mandatory_signing; +} + +/* + * [MS-SMB] 2.2.2.3.5 - SMB1 support for passing through + * query/set commands to the file system + */ +bool smbXcli_conn_support_passthrough(struct smbXcli_conn *conn) +{ + if (conn->protocol >= PROTOCOL_SMB2_02) { + return true; + } + + if (conn->smb1.capabilities & CAP_W2K_SMBS) { + return true; + } + + return false; +} + +void smbXcli_conn_set_sockopt(struct smbXcli_conn *conn, const char *options) +{ + set_socket_options(conn->sock_fd, options); +} + +const struct sockaddr_storage *smbXcli_conn_local_sockaddr(struct smbXcli_conn *conn) +{ + return &conn->local_ss; +} + +const struct sockaddr_storage *smbXcli_conn_remote_sockaddr(struct smbXcli_conn *conn) +{ + return &conn->remote_ss; +} + +const char *smbXcli_conn_remote_name(struct smbXcli_conn *conn) +{ + return conn->remote_name; +} + +uint16_t smbXcli_conn_max_requests(struct smbXcli_conn *conn) +{ + if (conn->protocol >= PROTOCOL_SMB2_02) { + /* + * TODO... + */ + return 1; + } + + return conn->smb1.server.max_mux; +} + +NTTIME smbXcli_conn_server_system_time(struct smbXcli_conn *conn) +{ + if (conn->protocol >= PROTOCOL_SMB2_02) { + return conn->smb2.server.system_time; + } + + return conn->smb1.server.system_time; +} + +const DATA_BLOB *smbXcli_conn_server_gss_blob(struct smbXcli_conn *conn) +{ + if (conn->protocol >= PROTOCOL_SMB2_02) { + return &conn->smb2.server.gss_blob; + } + + return &conn->smb1.server.gss_blob; +} + +const struct GUID *smbXcli_conn_server_guid(struct smbXcli_conn *conn) +{ + if (conn->protocol >= PROTOCOL_SMB2_02) { + return &conn->smb2.server.guid; + } + + return &conn->smb1.server.guid; +} + +bool smbXcli_conn_get_force_channel_sequence(struct smbXcli_conn *conn) +{ + return conn->smb2.force_channel_sequence; +} + +void smbXcli_conn_set_force_channel_sequence(struct smbXcli_conn *conn, + bool v) +{ + conn->smb2.force_channel_sequence = v; +} + +struct smbXcli_conn_samba_suicide_state { + struct smbXcli_conn *conn; + struct iovec iov; + uint8_t buf[9]; + struct tevent_req *write_req; +}; + +static void smbXcli_conn_samba_suicide_cleanup(struct tevent_req *req, + enum tevent_req_state req_state); +static void smbXcli_conn_samba_suicide_done(struct tevent_req *subreq); + +struct tevent_req *smbXcli_conn_samba_suicide_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint8_t exitcode) +{ + struct tevent_req *req, *subreq; + struct smbXcli_conn_samba_suicide_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct smbXcli_conn_samba_suicide_state); + if (req == NULL) { + return NULL; + } + state->conn = conn; + SIVAL(state->buf, 4, SMB_SUICIDE_PACKET); + SCVAL(state->buf, 8, exitcode); + _smb_setlen_nbt(state->buf, sizeof(state->buf)-4); + + if (conn->suicide_req != NULL) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + state->iov.iov_base = state->buf; + state->iov.iov_len = sizeof(state->buf); + + subreq = writev_send(state, ev, conn->outgoing, conn->sock_fd, + false, &state->iov, 1); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smbXcli_conn_samba_suicide_done, req); + state->write_req = subreq; + + tevent_req_set_cleanup_fn(req, smbXcli_conn_samba_suicide_cleanup); + + /* + * We need to use tevent_req_defer_callback() + * in order to allow smbXcli_conn_disconnect() + * to do a safe cleanup. + */ + tevent_req_defer_callback(req, ev); + conn->suicide_req = req; + + return req; +} + +static void smbXcli_conn_samba_suicide_cleanup(struct tevent_req *req, + enum tevent_req_state req_state) +{ + struct smbXcli_conn_samba_suicide_state *state = tevent_req_data( + req, struct smbXcli_conn_samba_suicide_state); + + TALLOC_FREE(state->write_req); + + if (state->conn == NULL) { + return; + } + + if (state->conn->suicide_req == req) { + state->conn->suicide_req = NULL; + } + state->conn = NULL; +} + +static void smbXcli_conn_samba_suicide_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct smbXcli_conn_samba_suicide_state *state = tevent_req_data( + req, struct smbXcli_conn_samba_suicide_state); + ssize_t nwritten; + int err; + + state->write_req = NULL; + + nwritten = writev_recv(subreq, &err); + TALLOC_FREE(subreq); + if (nwritten == -1) { + /* here, we need to notify all pending requests */ + NTSTATUS status = map_nt_error_from_unix_common(err); + smbXcli_conn_disconnect(state->conn, status); + return; + } + tevent_req_done(req); +} + +NTSTATUS smbXcli_conn_samba_suicide_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +NTSTATUS smbXcli_conn_samba_suicide(struct smbXcli_conn *conn, + uint8_t exitcode) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + bool ok; + + if (smbXcli_conn_has_async_calls(conn)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER_MIX; + goto fail; + } + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = smbXcli_conn_samba_suicide_send(frame, ev, conn, exitcode); + if (req == NULL) { + goto fail; + } + ok = tevent_req_poll_ntstatus(req, ev, &status); + if (!ok) { + goto fail; + } + status = smbXcli_conn_samba_suicide_recv(req); + fail: + TALLOC_FREE(frame); + return status; +} + +uint32_t smb1cli_conn_capabilities(struct smbXcli_conn *conn) +{ + return conn->smb1.capabilities; +} + +uint32_t smb1cli_conn_max_xmit(struct smbXcli_conn *conn) +{ + return conn->smb1.max_xmit; +} + +bool smb1cli_conn_req_possible(struct smbXcli_conn *conn) +{ + size_t pending = talloc_array_length(conn->pending); + uint16_t possible = conn->smb1.server.max_mux; + + if (pending >= possible) { + return false; + } + + return true; +} + +uint32_t smb1cli_conn_server_session_key(struct smbXcli_conn *conn) +{ + return conn->smb1.server.session_key; +} + +const uint8_t *smb1cli_conn_server_challenge(struct smbXcli_conn *conn) +{ + return conn->smb1.server.challenge; +} + +uint16_t smb1cli_conn_server_security_mode(struct smbXcli_conn *conn) +{ + return conn->smb1.server.security_mode; +} + +bool smb1cli_conn_server_readbraw(struct smbXcli_conn *conn) +{ + return conn->smb1.server.readbraw; +} + +bool smb1cli_conn_server_writebraw(struct smbXcli_conn *conn) +{ + return conn->smb1.server.writebraw; +} + +bool smb1cli_conn_server_lockread(struct smbXcli_conn *conn) +{ + return conn->smb1.server.lockread; +} + +bool smb1cli_conn_server_writeunlock(struct smbXcli_conn *conn) +{ + return conn->smb1.server.writeunlock; +} + +int smb1cli_conn_server_time_zone(struct smbXcli_conn *conn) +{ + return conn->smb1.server.time_zone; +} + +bool smb1cli_conn_activate_signing(struct smbXcli_conn *conn, + const DATA_BLOB user_session_key, + const DATA_BLOB response) +{ + return smb1_signing_activate(conn->smb1.signing, + user_session_key, + response); +} + +bool smb1cli_conn_check_signing(struct smbXcli_conn *conn, + const uint8_t *buf, uint32_t seqnum) +{ + const uint8_t *hdr = buf + NBT_HDR_SIZE; + size_t len = smb_len_nbt(buf); + + return smb1_signing_check_pdu(conn->smb1.signing, hdr, len, seqnum); +} + +bool smb1cli_conn_signing_is_active(struct smbXcli_conn *conn) +{ + return smb1_signing_is_active(conn->smb1.signing); +} + +void smb1cli_conn_set_encryption(struct smbXcli_conn *conn, + struct smb_trans_enc_state *es) +{ + /* Replace the old state, if any. */ + if (conn->smb1.trans_enc) { + TALLOC_FREE(conn->smb1.trans_enc); + } + conn->smb1.trans_enc = es; +} + +bool smb1cli_conn_encryption_on(struct smbXcli_conn *conn) +{ + return common_encryption_on(conn->smb1.trans_enc); +} + + +static NTSTATUS smb1cli_pull_raw_error(const uint8_t *hdr) +{ + uint32_t flags2 = SVAL(hdr, HDR_FLG2); + NTSTATUS status = NT_STATUS(IVAL(hdr, HDR_RCLS)); + + if (NT_STATUS_IS_OK(status)) { + return NT_STATUS_OK; + } + + if (flags2 & FLAGS2_32_BIT_ERROR_CODES) { + return status; + } + + return NT_STATUS_DOS(CVAL(hdr, HDR_RCLS), SVAL(hdr, HDR_ERR)); +} + +/** + * Is the SMB command able to hold an AND_X successor + * @param[in] cmd The SMB command in question + * @retval Can we add a chained request after "cmd"? + */ +bool smb1cli_is_andx_req(uint8_t cmd) +{ + switch (cmd) { + case SMBtconX: + case SMBlockingX: + case SMBopenX: + case SMBreadX: + case SMBwriteX: + case SMBsesssetupX: + case SMBulogoffX: + case SMBntcreateX: + return true; + break; + default: + break; + } + + return false; +} + +static uint16_t smb1cli_alloc_mid(struct smbXcli_conn *conn) +{ + size_t num_pending = talloc_array_length(conn->pending); + uint16_t result; + + if (conn->protocol == PROTOCOL_NONE) { + /* + * This is what windows sends on the SMB1 Negprot request + * and some vendors reuse the SMB1 MID as SMB2 sequence number. + */ + return 0; + } + + while (true) { + size_t i; + + result = conn->smb1.mid++; + if ((result == 0) || (result == 0xffff)) { + continue; + } + + for (i=0; i<num_pending; i++) { + if (result == smb1cli_req_mid(conn->pending[i])) { + break; + } + } + + if (i == num_pending) { + return result; + } + } +} + +static NTSTATUS smbXcli_req_cancel_write_req(struct tevent_req *req) +{ + struct smbXcli_req_state *state = + tevent_req_data(req, + struct smbXcli_req_state); + struct smbXcli_conn *conn = state->conn; + size_t num_pending = talloc_array_length(conn->pending); + ssize_t ret; + int err; + bool ok; + + if (state->write_req == NULL) { + return NT_STATUS_OK; + } + + /* + * Check if it's possible to cancel the request. + * If the result is true it's not too late. + * See writev_cancel(). + */ + ok = tevent_req_cancel(state->write_req); + if (ok) { + TALLOC_FREE(state->write_req); + + if (conn->protocol >= PROTOCOL_SMB2_02) { + /* + * SMB2 has a sane signing state. + */ + return NT_STATUS_OK; + } + + if (num_pending > 1) { + /* + * We have more pending requests following us. This + * means the signing state will be broken for them. + * + * As a solution we could add the requests directly to + * our outgoing queue and do the signing in the trigger + * function and then use writev_send() without passing a + * queue. That way we'll only sign packets we're most + * likely send to the wire. + */ + return NT_STATUS_REQUEST_OUT_OF_SEQUENCE; + } + + /* + * If we're the only request that's + * pending, we're able to recover the signing + * state. + */ + smb1_signing_cancel_reply(conn->smb1.signing, + state->smb1.one_way_seqnum); + return NT_STATUS_OK; + } + + ret = writev_recv(state->write_req, &err); + TALLOC_FREE(state->write_req); + if (ret == -1) { + return map_nt_error_from_unix_common(err); + } + + return NT_STATUS_OK; +} + +void smbXcli_req_unset_pending(struct tevent_req *req) +{ + struct smbXcli_req_state *state = + tevent_req_data(req, + struct smbXcli_req_state); + struct smbXcli_conn *conn = state->conn; + size_t num_pending = talloc_array_length(conn->pending); + size_t i; + NTSTATUS cancel_status; + + cancel_status = smbXcli_req_cancel_write_req(req); + + if (state->smb1.mid != 0) { + /* + * This is a [nt]trans[2] request which waits + * for more than one reply. + */ + if (!NT_STATUS_IS_OK(cancel_status)) { + /* + * If the write_req cancel didn't work + * we can't use the connection anymore. + */ + smbXcli_conn_disconnect(conn, cancel_status); + return; + } + return; + } + + tevent_req_set_cleanup_fn(req, NULL); + + if (num_pending == 1) { + /* + * The pending read_smb tevent_req is a child of + * conn->pending. So if nothing is pending anymore, we need to + * delete the socket read fde. + */ + /* TODO: smbXcli_conn_cancel_read_req */ + TALLOC_FREE(conn->pending); + conn->read_smb_req = NULL; + + if (!NT_STATUS_IS_OK(cancel_status)) { + /* + * If the write_req cancel didn't work + * we can't use the connection anymore. + */ + smbXcli_conn_disconnect(conn, cancel_status); + return; + } + return; + } + + for (i=0; i<num_pending; i++) { + if (req == conn->pending[i]) { + break; + } + } + if (i == num_pending) { + /* + * Something's seriously broken. Just returning here is the + * right thing nevertheless, the point of this routine is to + * remove ourselves from conn->pending. + */ + + if (!NT_STATUS_IS_OK(cancel_status)) { + /* + * If the write_req cancel didn't work + * we can't use the connection anymore. + */ + smbXcli_conn_disconnect(conn, cancel_status); + return; + } + return; + } + + /* + * Remove ourselves from the conn->pending array + */ + for (; i < (num_pending - 1); i++) { + conn->pending[i] = conn->pending[i+1]; + } + + /* + * No NULL check here, we're shrinking by sizeof(void *), and + * talloc_realloc just adjusts the size for this. + */ + conn->pending = talloc_realloc(NULL, conn->pending, struct tevent_req *, + num_pending - 1); + + if (!NT_STATUS_IS_OK(cancel_status)) { + /* + * If the write_req cancel didn't work + * we can't use the connection anymore. + */ + smbXcli_conn_disconnect(conn, cancel_status); + return; + } + return; +} + +static void smbXcli_req_cleanup(struct tevent_req *req, + enum tevent_req_state req_state) +{ + struct smbXcli_req_state *state = + tevent_req_data(req, + struct smbXcli_req_state); + struct smbXcli_conn *conn = state->conn; + NTSTATUS cancel_status; + + switch (req_state) { + case TEVENT_REQ_RECEIVED: + /* + * Make sure we really remove it from + * the pending array on destruction. + * + * smbXcli_req_unset_pending() calls + * smbXcli_req_cancel_write_req() internal + */ + state->smb1.mid = 0; + smbXcli_req_unset_pending(req); + return; + default: + cancel_status = smbXcli_req_cancel_write_req(req); + if (!NT_STATUS_IS_OK(cancel_status)) { + /* + * If the write_req cancel didn't work + * we can't use the connection anymore. + */ + smbXcli_conn_disconnect(conn, cancel_status); + return; + } + return; + } +} + +static bool smb1cli_req_cancel(struct tevent_req *req); +static bool smb2cli_req_cancel(struct tevent_req *req); + +static bool smbXcli_req_cancel(struct tevent_req *req) +{ + struct smbXcli_req_state *state = + tevent_req_data(req, + struct smbXcli_req_state); + + if (!smbXcli_conn_is_connected(state->conn)) { + return false; + } + + if (state->conn->protocol == PROTOCOL_NONE) { + return false; + } + + if (state->conn->protocol >= PROTOCOL_SMB2_02) { + return smb2cli_req_cancel(req); + } + + return smb1cli_req_cancel(req); +} + +static bool smbXcli_conn_receive_next(struct smbXcli_conn *conn); + +bool smbXcli_req_set_pending(struct tevent_req *req) +{ + struct smbXcli_req_state *state = + tevent_req_data(req, + struct smbXcli_req_state); + struct smbXcli_conn *conn; + struct tevent_req **pending; + size_t num_pending; + + conn = state->conn; + + if (!smbXcli_conn_is_connected(conn)) { + return false; + } + + num_pending = talloc_array_length(conn->pending); + + pending = talloc_realloc(conn, conn->pending, struct tevent_req *, + num_pending+1); + if (pending == NULL) { + return false; + } + pending[num_pending] = req; + conn->pending = pending; + tevent_req_set_cleanup_fn(req, smbXcli_req_cleanup); + tevent_req_set_cancel_fn(req, smbXcli_req_cancel); + + if (!smbXcli_conn_receive_next(conn)) { + /* + * the caller should notify the current request + * + * And all other pending requests get notified + * by smbXcli_conn_disconnect(). + */ + smbXcli_req_unset_pending(req); + smbXcli_conn_disconnect(conn, NT_STATUS_NO_MEMORY); + return false; + } + + return true; +} + +static void smbXcli_conn_received(struct tevent_req *subreq); + +static bool smbXcli_conn_receive_next(struct smbXcli_conn *conn) +{ + size_t num_pending = talloc_array_length(conn->pending); + struct tevent_req *req; + struct smbXcli_req_state *state; + + if (conn->read_smb_req != NULL) { + return true; + } + + if (num_pending == 0) { + if (conn->smb2.mid < UINT64_MAX) { + /* no more pending requests, so we are done for now */ + return true; + } + + /* + * If there are no more SMB2 requests possible, + * because we are out of message ids, + * we need to disconnect. + */ + smbXcli_conn_disconnect(conn, NT_STATUS_CONNECTION_ABORTED); + return true; + } + + req = conn->pending[0]; + state = tevent_req_data(req, struct smbXcli_req_state); + + /* + * We're the first ones, add the read_smb request that waits for the + * answer from the server + */ + conn->read_smb_req = read_smb_send(conn->pending, + state->ev, + conn->sock_fd); + if (conn->read_smb_req == NULL) { + return false; + } + tevent_req_set_callback(conn->read_smb_req, smbXcli_conn_received, conn); + return true; +} + +void smbXcli_conn_disconnect(struct smbXcli_conn *conn, NTSTATUS status) +{ + struct smbXcli_session *session; + int sock_fd = conn->sock_fd; + + tevent_queue_stop(conn->outgoing); + + conn->sock_fd = -1; + + session = conn->sessions; + if (talloc_array_length(conn->pending) == 0) { + /* + * if we do not have pending requests + * there is no need to update the channel_sequence + */ + session = NULL; + } + for (; session; session = session->next) { + smb2cli_session_increment_channel_sequence(session); + } + + if (conn->suicide_req != NULL) { + /* + * smbXcli_conn_samba_suicide_send() + * used tevent_req_defer_callback() already. + */ + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(conn->suicide_req, status); + } + conn->suicide_req = NULL; + } + + /* + * Cancel all pending requests. We do not do a for-loop walking + * conn->pending because that array changes in + * smbXcli_req_unset_pending. + */ + while (conn->pending != NULL && + talloc_array_length(conn->pending) > 0) { + struct tevent_req *req; + struct smbXcli_req_state *state; + struct tevent_req **chain; + size_t num_chained; + size_t i; + + req = conn->pending[0]; + state = tevent_req_data(req, struct smbXcli_req_state); + + if (state->smb1.chained_requests == NULL) { + bool in_progress; + + /* + * We're dead. No point waiting for trans2 + * replies. + */ + state->smb1.mid = 0; + + smbXcli_req_unset_pending(req); + + if (NT_STATUS_IS_OK(status)) { + /* do not notify the callers */ + continue; + } + + in_progress = tevent_req_is_in_progress(req); + if (!in_progress) { + /* + * already finished + */ + continue; + } + + /* + * we need to defer the callback, because we may notify + * more then one caller. + */ + tevent_req_defer_callback(req, state->ev); + tevent_req_nterror(req, status); + continue; + } + + chain = talloc_move(conn, &state->smb1.chained_requests); + num_chained = talloc_array_length(chain); + + for (i=0; i<num_chained; i++) { + bool in_progress; + + req = chain[i]; + state = tevent_req_data(req, struct smbXcli_req_state); + + /* + * We're dead. No point waiting for trans2 + * replies. + */ + state->smb1.mid = 0; + + smbXcli_req_unset_pending(req); + + if (NT_STATUS_IS_OK(status)) { + /* do not notify the callers */ + continue; + } + + in_progress = tevent_req_is_in_progress(req); + if (!in_progress) { + /* + * already finished + */ + continue; + } + + /* + * we need to defer the callback, because we may notify + * more than one caller. + */ + tevent_req_defer_callback(req, state->ev); + tevent_req_nterror(req, status); + } + TALLOC_FREE(chain); + } + + if (sock_fd != -1) { + close(sock_fd); + } +} + +/* + * Fetch a smb request's mid. Only valid after the request has been sent by + * smb1cli_req_send(). + */ +uint16_t smb1cli_req_mid(struct tevent_req *req) +{ + struct smbXcli_req_state *state = + tevent_req_data(req, + struct smbXcli_req_state); + + if (state->smb1.mid != 0) { + return state->smb1.mid; + } + + return SVAL(state->smb1.hdr, HDR_MID); +} + +void smb1cli_req_set_mid(struct tevent_req *req, uint16_t mid) +{ + struct smbXcli_req_state *state = + tevent_req_data(req, + struct smbXcli_req_state); + + state->smb1.mid = mid; +} + +uint32_t smb1cli_req_seqnum(struct tevent_req *req) +{ + struct smbXcli_req_state *state = + tevent_req_data(req, + struct smbXcli_req_state); + + return state->smb1.seqnum; +} + +void smb1cli_req_set_seqnum(struct tevent_req *req, uint32_t seqnum) +{ + struct smbXcli_req_state *state = + tevent_req_data(req, + struct smbXcli_req_state); + + state->smb1.seqnum = seqnum; +} + +static size_t smbXcli_iov_len(const struct iovec *iov, int count) +{ + ssize_t ret = iov_buflen(iov, count); + + /* Ignore the overflow case for now ... */ + return ret; +} + +static void smb1cli_req_flags(enum protocol_types protocol, + uint32_t smb1_capabilities, + uint8_t smb_command, + uint8_t additional_flags, + uint8_t clear_flags, + uint8_t *_flags, + uint16_t additional_flags2, + uint16_t clear_flags2, + uint16_t *_flags2) +{ + uint8_t flags = 0; + uint16_t flags2 = 0; + + if (protocol >= PROTOCOL_LANMAN1) { + flags |= FLAG_CASELESS_PATHNAMES; + flags |= FLAG_CANONICAL_PATHNAMES; + } + + if (protocol >= PROTOCOL_LANMAN2) { + flags2 |= FLAGS2_LONG_PATH_COMPONENTS; + flags2 |= FLAGS2_EXTENDED_ATTRIBUTES; + } + + if (protocol >= PROTOCOL_NT1) { + flags2 |= FLAGS2_IS_LONG_NAME; + + if (smb1_capabilities & CAP_UNICODE) { + flags2 |= FLAGS2_UNICODE_STRINGS; + } + if (smb1_capabilities & CAP_STATUS32) { + flags2 |= FLAGS2_32_BIT_ERROR_CODES; + } + if (smb1_capabilities & CAP_EXTENDED_SECURITY) { + flags2 |= FLAGS2_EXTENDED_SECURITY; + } + } + + flags |= additional_flags; + flags &= ~clear_flags; + flags2 |= additional_flags2; + flags2 &= ~clear_flags2; + + *_flags = flags; + *_flags2 = flags2; +} + +static void smb1cli_req_cancel_done(struct tevent_req *subreq); + +static bool smb1cli_req_cancel(struct tevent_req *req) +{ + struct smbXcli_req_state *state = + tevent_req_data(req, + struct smbXcli_req_state); + uint8_t flags; + uint16_t flags2; + uint32_t pid; + uint16_t mid; + struct tevent_req *subreq; + NTSTATUS status; + + flags = CVAL(state->smb1.hdr, HDR_FLG); + flags2 = SVAL(state->smb1.hdr, HDR_FLG2); + pid = SVAL(state->smb1.hdr, HDR_PID); + pid |= SVAL(state->smb1.hdr, HDR_PIDHIGH)<<16; + mid = SVAL(state->smb1.hdr, HDR_MID); + + subreq = smb1cli_req_create(state, state->ev, + state->conn, + SMBntcancel, + flags, 0, + flags2, 0, + 0, /* timeout */ + pid, + state->tcon, + state->session, + 0, NULL, /* vwv */ + 0, NULL); /* bytes */ + if (subreq == NULL) { + return false; + } + smb1cli_req_set_mid(subreq, mid); + + status = smb1cli_req_chain_submit(&subreq, 1); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(subreq); + return false; + } + smb1cli_req_set_mid(subreq, 0); + + tevent_req_set_callback(subreq, smb1cli_req_cancel_done, NULL); + + return true; +} + +static void smb1cli_req_cancel_done(struct tevent_req *subreq) +{ + /* we do not care about the result */ + TALLOC_FREE(subreq); +} + +struct tevent_req *smb1cli_req_create(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint8_t smb_command, + uint8_t additional_flags, + uint8_t clear_flags, + uint16_t additional_flags2, + uint16_t clear_flags2, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + uint8_t wct, uint16_t *vwv, + int iov_count, + struct iovec *bytes_iov) +{ + struct tevent_req *req; + struct smbXcli_req_state *state; + uint8_t flags = 0; + uint16_t flags2 = 0; + uint16_t uid = 0; + uint16_t tid = 0; + ssize_t num_bytes; + + if (iov_count > MAX_SMB_IOV) { + /* + * Should not happen :-) + */ + return NULL; + } + + req = tevent_req_create(mem_ctx, &state, + struct smbXcli_req_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->conn = conn; + state->session = session; + state->tcon = tcon; + + if (session) { + uid = session->smb1.session_id; + } + + if (tcon) { + tid = tcon->smb1.tcon_id; + + if (tcon->fs_attributes & FILE_CASE_SENSITIVE_SEARCH) { + clear_flags |= FLAG_CASELESS_PATHNAMES; + } else { + /* Default setting, case insensitive. */ + additional_flags |= FLAG_CASELESS_PATHNAMES; + } + + if (smbXcli_conn_dfs_supported(conn) && + smbXcli_tcon_is_dfs_share(tcon)) + { + additional_flags2 |= FLAGS2_DFS_PATHNAMES; + } + } + + state->smb1.recv_cmd = 0xFF; + state->smb1.recv_status = NT_STATUS_INTERNAL_ERROR; + state->smb1.recv_iov = talloc_zero_array(state, struct iovec, 3); + if (state->smb1.recv_iov == NULL) { + TALLOC_FREE(req); + return NULL; + } + + smb1cli_req_flags(conn->protocol, + conn->smb1.capabilities, + smb_command, + additional_flags, + clear_flags, + &flags, + additional_flags2, + clear_flags2, + &flags2); + + SIVAL(state->smb1.hdr, 0, SMB_MAGIC); + SCVAL(state->smb1.hdr, HDR_COM, smb_command); + SIVAL(state->smb1.hdr, HDR_RCLS, NT_STATUS_V(NT_STATUS_OK)); + SCVAL(state->smb1.hdr, HDR_FLG, flags); + SSVAL(state->smb1.hdr, HDR_FLG2, flags2); + SSVAL(state->smb1.hdr, HDR_PIDHIGH, pid >> 16); + SSVAL(state->smb1.hdr, HDR_TID, tid); + SSVAL(state->smb1.hdr, HDR_PID, pid); + SSVAL(state->smb1.hdr, HDR_UID, uid); + SSVAL(state->smb1.hdr, HDR_MID, 0); /* this comes later */ + SCVAL(state->smb1.hdr, HDR_WCT, wct); + + state->smb1.vwv = vwv; + + num_bytes = iov_buflen(bytes_iov, iov_count); + if (num_bytes == -1) { + /* + * I'd love to add a check for num_bytes<=UINT16_MAX here, but + * the smbclient->samba connections can lie and transfer more. + */ + TALLOC_FREE(req); + return NULL; + } + + SSVAL(state->smb1.bytecount_buf, 0, num_bytes); + + state->smb1.iov[0].iov_base = (void *)state->length_hdr; + state->smb1.iov[0].iov_len = sizeof(state->length_hdr); + state->smb1.iov[1].iov_base = (void *)state->smb1.hdr; + state->smb1.iov[1].iov_len = sizeof(state->smb1.hdr); + state->smb1.iov[2].iov_base = (void *)state->smb1.vwv; + state->smb1.iov[2].iov_len = wct * sizeof(uint16_t); + state->smb1.iov[3].iov_base = (void *)state->smb1.bytecount_buf; + state->smb1.iov[3].iov_len = sizeof(uint16_t); + + if (iov_count != 0) { + memcpy(&state->smb1.iov[4], bytes_iov, + iov_count * sizeof(*bytes_iov)); + } + state->smb1.iov_count = iov_count + 4; + + if (timeout_msec > 0) { + state->endtime = timeval_current_ofs_msec(timeout_msec); + if (!tevent_req_set_endtime(req, ev, state->endtime)) { + return req; + } + } + + switch (smb_command) { + case SMBtranss: + case SMBtranss2: + case SMBnttranss: + state->one_way = true; + break; + case SMBntcancel: + state->one_way = true; + state->smb1.one_way_seqnum = true; + break; + case SMBlockingX: + if ((wct == 8) && + (CVAL(vwv+3, 0) == LOCKING_ANDX_OPLOCK_RELEASE)) { + state->one_way = true; + } + break; + } + + return req; +} + +static NTSTATUS smb1cli_conn_signv(struct smbXcli_conn *conn, + struct iovec *iov, int iov_count, + uint32_t *seqnum, + bool one_way_seqnum) +{ + TALLOC_CTX *frame = NULL; + NTSTATUS status; + uint8_t *buf; + + /* + * Obvious optimization: Make cli_calculate_sign_mac work with struct + * iovec directly. MD5Update would do that just fine. + */ + + if (iov_count < 4) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + if (iov[0].iov_len != NBT_HDR_SIZE) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + if (iov[1].iov_len != (MIN_SMB_SIZE-sizeof(uint16_t))) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + if (iov[2].iov_len > (0xFF * sizeof(uint16_t))) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + if (iov[3].iov_len != sizeof(uint16_t)) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + frame = talloc_stackframe(); + + buf = iov_concat(frame, &iov[1], iov_count - 1); + if (buf == NULL) { + return NT_STATUS_NO_MEMORY; + } + + *seqnum = smb1_signing_next_seqnum(conn->smb1.signing, + one_way_seqnum); + status = smb1_signing_sign_pdu(conn->smb1.signing, + buf, + talloc_get_size(buf), + *seqnum); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + memcpy(iov[1].iov_base, buf, iov[1].iov_len); + + TALLOC_FREE(frame); + return NT_STATUS_OK; +} + +static void smb1cli_req_writev_done(struct tevent_req *subreq); +static NTSTATUS smb1cli_conn_dispatch_incoming(struct smbXcli_conn *conn, + TALLOC_CTX *tmp_mem, + uint8_t *inbuf); + +static NTSTATUS smb1cli_req_writev_submit(struct tevent_req *req, + struct smbXcli_req_state *state, + struct iovec *iov, int iov_count) +{ + struct tevent_req *subreq; + NTSTATUS status; + uint8_t cmd; + uint16_t mid; + ssize_t nbtlen; + + if (!smbXcli_conn_is_connected(state->conn)) { + return NT_STATUS_CONNECTION_DISCONNECTED; + } + + if (state->conn->protocol > PROTOCOL_NT1) { + DBG_ERR("called for dialect[%s] server[%s]\n", + smb_protocol_types_string(state->conn->protocol), + smbXcli_conn_remote_name(state->conn)); + return NT_STATUS_REVISION_MISMATCH; + } + + if (iov_count < 4) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + if (iov[0].iov_len != NBT_HDR_SIZE) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + if (iov[1].iov_len != (MIN_SMB_SIZE-sizeof(uint16_t))) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + if (iov[2].iov_len > (0xFF * sizeof(uint16_t))) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + if (iov[3].iov_len != sizeof(uint16_t)) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + cmd = CVAL(iov[1].iov_base, HDR_COM); + if (cmd == SMBreadBraw) { + if (smbXcli_conn_has_async_calls(state->conn)) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + state->conn->smb1.read_braw_req = req; + } + + if (state->smb1.mid != 0) { + mid = state->smb1.mid; + } else { + mid = smb1cli_alloc_mid(state->conn); + } + SSVAL(iov[1].iov_base, HDR_MID, mid); + + nbtlen = iov_buflen(&iov[1], iov_count-1); + if ((nbtlen == -1) || (nbtlen > 0x1FFFF)) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + _smb_setlen_nbt(iov[0].iov_base, nbtlen); + + status = smb1cli_conn_signv(state->conn, iov, iov_count, + &state->smb1.seqnum, + state->smb1.one_way_seqnum); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * If we supported multiple encryption contexts + * here we'd look up based on tid. + */ + if (common_encryption_on(state->conn->smb1.trans_enc)) { + char *buf, *enc_buf; + + buf = (char *)iov_concat(talloc_tos(), iov, iov_count); + if (buf == NULL) { + return NT_STATUS_NO_MEMORY; + } + status = common_encrypt_buffer(state->conn->smb1.trans_enc, + (char *)buf, &enc_buf); + TALLOC_FREE(buf); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Error in encrypting client message: %s\n", + nt_errstr(status))); + return status; + } + buf = (char *)talloc_memdup(state, enc_buf, + smb_len_nbt(enc_buf)+4); + SAFE_FREE(enc_buf); + if (buf == NULL) { + return NT_STATUS_NO_MEMORY; + } + iov[0].iov_base = (void *)buf; + iov[0].iov_len = talloc_get_size(buf); + iov_count = 1; + } + + if (state->conn->dispatch_incoming == NULL) { + state->conn->dispatch_incoming = smb1cli_conn_dispatch_incoming; + } + + if (!smbXcli_req_set_pending(req)) { + return NT_STATUS_NO_MEMORY; + } + + tevent_req_set_cancel_fn(req, smbXcli_req_cancel); + + subreq = writev_send(state, state->ev, state->conn->outgoing, + state->conn->sock_fd, false, iov, iov_count); + if (subreq == NULL) { + return NT_STATUS_NO_MEMORY; + } + tevent_req_set_callback(subreq, smb1cli_req_writev_done, req); + state->write_req = subreq; + + return NT_STATUS_OK; +} + +struct tevent_req *smb1cli_req_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint8_t smb_command, + uint8_t additional_flags, + uint8_t clear_flags, + uint16_t additional_flags2, + uint16_t clear_flags2, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + uint8_t wct, uint16_t *vwv, + uint32_t num_bytes, + const uint8_t *bytes) +{ + struct tevent_req *req; + struct iovec iov; + NTSTATUS status; + + iov.iov_base = discard_const_p(void, bytes); + iov.iov_len = num_bytes; + + req = smb1cli_req_create(mem_ctx, ev, conn, smb_command, + additional_flags, clear_flags, + additional_flags2, clear_flags2, + timeout_msec, + pid, tcon, session, + wct, vwv, 1, &iov); + if (req == NULL) { + return NULL; + } + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + status = smb1cli_req_chain_submit(&req, 1); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + return req; +} + +static void smb1cli_req_writev_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct smbXcli_req_state *state = + tevent_req_data(req, + struct smbXcli_req_state); + ssize_t nwritten; + int err; + + state->write_req = NULL; + + nwritten = writev_recv(subreq, &err); + TALLOC_FREE(subreq); + if (nwritten == -1) { + /* here, we need to notify all pending requests */ + NTSTATUS status = map_nt_error_from_unix_common(err); + smbXcli_conn_disconnect(state->conn, status); + return; + } + + if (state->one_way) { + state->inbuf = NULL; + tevent_req_done(req); + return; + } +} + +static void smbXcli_conn_received(struct tevent_req *subreq) +{ + struct smbXcli_conn *conn = + tevent_req_callback_data(subreq, + struct smbXcli_conn); + TALLOC_CTX *frame = talloc_stackframe(); + NTSTATUS status; + uint8_t *inbuf; + ssize_t received; + int err; + + if (subreq != conn->read_smb_req) { + DEBUG(1, ("Internal error: cli_smb_received called with " + "unexpected subreq\n")); + smbXcli_conn_disconnect(conn, NT_STATUS_INTERNAL_ERROR); + TALLOC_FREE(frame); + return; + } + conn->read_smb_req = NULL; + + received = read_smb_recv(subreq, frame, &inbuf, &err); + TALLOC_FREE(subreq); + if (received == -1) { + status = map_nt_error_from_unix_common(err); + smbXcli_conn_disconnect(conn, status); + TALLOC_FREE(frame); + return; + } + + status = conn->dispatch_incoming(conn, frame, inbuf); + TALLOC_FREE(frame); + if (NT_STATUS_IS_OK(status)) { + /* + * We should not do any more processing + * as the dispatch function called + * tevent_req_done(). + */ + return; + } + + if (!NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) { + /* + * We got an error, so notify all pending requests + */ + smbXcli_conn_disconnect(conn, status); + return; + } + + /* + * We got NT_STATUS_RETRY, so we may ask for a + * next incoming pdu. + */ + if (!smbXcli_conn_receive_next(conn)) { + smbXcli_conn_disconnect(conn, NT_STATUS_NO_MEMORY); + } +} + +static NTSTATUS smb1cli_inbuf_parse_chain(uint8_t *buf, TALLOC_CTX *mem_ctx, + struct iovec **piov, int *pnum_iov) +{ + struct iovec *iov; + size_t num_iov; + size_t buflen; + size_t taken; + size_t remaining; + uint8_t *hdr; + uint8_t cmd; + uint32_t wct_ofs; + NTSTATUS status; + size_t min_size = MIN_SMB_SIZE; + + buflen = smb_len_tcp(buf); + taken = 0; + + hdr = buf + NBT_HDR_SIZE; + + status = smb1cli_pull_raw_error(hdr); + if (NT_STATUS_IS_ERR(status)) { + /* + * This is an ugly hack to support OS/2 + * which skips the byte_count in the DATA block + * on some error responses. + * + * See bug #9096 + */ + min_size -= sizeof(uint16_t); + } + + if (buflen < min_size) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + /* + * This returns iovec elements in the following order: + * + * - SMB header + * + * - Parameter Block + * - Data Block + * + * - Parameter Block + * - Data Block + * + * - Parameter Block + * - Data Block + */ + num_iov = 1; + + iov = talloc_array(mem_ctx, struct iovec, num_iov); + if (iov == NULL) { + return NT_STATUS_NO_MEMORY; + } + iov[0].iov_base = hdr; + iov[0].iov_len = HDR_WCT; + taken += HDR_WCT; + + cmd = CVAL(hdr, HDR_COM); + wct_ofs = HDR_WCT; + + while (true) { + size_t len = buflen - taken; + struct iovec *cur; + struct iovec *iov_tmp; + uint8_t wct; + uint32_t bcc_ofs; + uint16_t bcc; + size_t needed; + + /* + * we need at least WCT + */ + needed = sizeof(uint8_t); + if (len < needed) { + DEBUG(10, ("%s: %d bytes left, expected at least %d\n", + __location__, (int)len, (int)needed)); + goto inval; + } + + /* + * Now we check if the specified words are there + */ + wct = CVAL(hdr, wct_ofs); + needed += wct * sizeof(uint16_t); + if (len < needed) { + DEBUG(10, ("%s: %d bytes left, expected at least %d\n", + __location__, (int)len, (int)needed)); + goto inval; + } + + if ((num_iov == 1) && + (len == needed) && + NT_STATUS_IS_ERR(status)) + { + /* + * This is an ugly hack to support OS/2 + * which skips the byte_count in the DATA block + * on some error responses. + * + * See bug #9096 + */ + iov_tmp = talloc_realloc(mem_ctx, iov, struct iovec, + num_iov + 2); + if (iov_tmp == NULL) { + TALLOC_FREE(iov); + return NT_STATUS_NO_MEMORY; + } + iov = iov_tmp; + cur = &iov[num_iov]; + num_iov += 2; + + cur[0].iov_len = 0; + cur[0].iov_base = hdr + (wct_ofs + sizeof(uint8_t)); + cur[1].iov_len = 0; + cur[1].iov_base = cur[0].iov_base; + + taken += needed; + break; + } + + /* + * we need at least BCC + */ + needed += sizeof(uint16_t); + if (len < needed) { + DEBUG(10, ("%s: %d bytes left, expected at least %d\n", + __location__, (int)len, (int)needed)); + goto inval; + } + + /* + * Now we check if the specified bytes are there + */ + bcc_ofs = wct_ofs + sizeof(uint8_t) + wct * sizeof(uint16_t); + bcc = SVAL(hdr, bcc_ofs); + needed += bcc * sizeof(uint8_t); + if (len < needed) { + DEBUG(10, ("%s: %d bytes left, expected at least %d\n", + __location__, (int)len, (int)needed)); + goto inval; + } + + /* + * we allocate 2 iovec structures for words and bytes + */ + iov_tmp = talloc_realloc(mem_ctx, iov, struct iovec, + num_iov + 2); + if (iov_tmp == NULL) { + TALLOC_FREE(iov); + return NT_STATUS_NO_MEMORY; + } + iov = iov_tmp; + cur = &iov[num_iov]; + num_iov += 2; + + cur[0].iov_len = wct * sizeof(uint16_t); + cur[0].iov_base = hdr + (wct_ofs + sizeof(uint8_t)); + cur[1].iov_len = bcc * sizeof(uint8_t); + cur[1].iov_base = hdr + (bcc_ofs + sizeof(uint16_t)); + + taken += needed; + + if (!smb1cli_is_andx_req(cmd)) { + /* + * If the current command does not have AndX chanining + * we are done. + */ + break; + } + + if (wct == 0 && bcc == 0) { + /* + * An empty response also ends the chain, + * most likely with an error. + */ + break; + } + + if (wct < 2) { + DEBUG(10, ("%s: wct[%d] < 2 for cmd[0x%02X]\n", + __location__, (int)wct, (int)cmd)); + goto inval; + } + cmd = CVAL(cur[0].iov_base, 0); + if (cmd == 0xFF) { + /* + * If it is the end of the chain we are also done. + */ + break; + } + wct_ofs = SVAL(cur[0].iov_base, 2); + + if (wct_ofs < taken) { + goto inval; + } + if (wct_ofs > buflen) { + goto inval; + } + + /* + * we consumed everything up to the start of the next + * parameter block. + */ + taken = wct_ofs; + } + + remaining = buflen - taken; + + if (remaining > 0 && num_iov >= 3) { + /* + * The last DATA block gets the remaining + * bytes, this is needed to support + * CAP_LARGE_WRITEX and CAP_LARGE_READX. + */ + iov[num_iov-1].iov_len += remaining; + } + + *piov = iov; + *pnum_iov = num_iov; + return NT_STATUS_OK; + +inval: + TALLOC_FREE(iov); + return NT_STATUS_INVALID_NETWORK_RESPONSE; +} + +static NTSTATUS smb1cli_conn_dispatch_incoming(struct smbXcli_conn *conn, + TALLOC_CTX *tmp_mem, + uint8_t *inbuf) +{ + struct tevent_req *req; + struct smbXcli_req_state *state; + NTSTATUS status; + size_t num_pending; + size_t i; + uint8_t cmd; + uint16_t mid; + bool oplock_break; + uint8_t *inhdr = inbuf + NBT_HDR_SIZE; + size_t len = smb_len_tcp(inbuf); + struct iovec *iov = NULL; + int num_iov = 0; + struct tevent_req **chain = NULL; + size_t num_chained = 0; + size_t num_responses = 0; + + if (conn->smb1.read_braw_req != NULL) { + req = conn->smb1.read_braw_req; + conn->smb1.read_braw_req = NULL; + state = tevent_req_data(req, struct smbXcli_req_state); + + smbXcli_req_unset_pending(req); + + if (state->smb1.recv_iov == NULL) { + /* + * For requests with more than + * one response, we have to readd the + * recv_iov array. + */ + state->smb1.recv_iov = talloc_zero_array(state, + struct iovec, + 3); + if (tevent_req_nomem(state->smb1.recv_iov, req)) { + return NT_STATUS_OK; + } + } + + state->smb1.recv_iov[0].iov_base = (void *)(inhdr); + state->smb1.recv_iov[0].iov_len = len; + ZERO_STRUCT(state->smb1.recv_iov[1]); + ZERO_STRUCT(state->smb1.recv_iov[2]); + + state->smb1.recv_cmd = SMBreadBraw; + state->smb1.recv_status = NT_STATUS_OK; + state->inbuf = talloc_move(state->smb1.recv_iov, &inbuf); + + tevent_req_done(req); + return NT_STATUS_OK; + } + + if ((IVAL(inhdr, 0) != SMB_MAGIC) /* 0xFF"SMB" */ + && (SVAL(inhdr, 0) != 0x45ff)) /* 0xFF"E" */ { + DEBUG(10, ("Got non-SMB PDU\n")); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + /* + * If we supported multiple encryption contexts + * here we'd look up based on tid. + */ + if (common_encryption_on(conn->smb1.trans_enc) + && (CVAL(inbuf, 0) == 0)) { + uint16_t enc_ctx_num; + + status = get_enc_ctx_num(inbuf, &enc_ctx_num); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("get_enc_ctx_num returned %s\n", + nt_errstr(status))); + return status; + } + + if (enc_ctx_num != conn->smb1.trans_enc->enc_ctx_num) { + DEBUG(10, ("wrong enc_ctx %d, expected %d\n", + enc_ctx_num, + conn->smb1.trans_enc->enc_ctx_num)); + return NT_STATUS_INVALID_HANDLE; + } + + status = common_decrypt_buffer(conn->smb1.trans_enc, + (char *)inbuf); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("common_decrypt_buffer returned %s\n", + nt_errstr(status))); + return status; + } + inhdr = inbuf + NBT_HDR_SIZE; + len = smb_len_nbt(inbuf); + } + + mid = SVAL(inhdr, HDR_MID); + num_pending = talloc_array_length(conn->pending); + + for (i=0; i<num_pending; i++) { + if (mid == smb1cli_req_mid(conn->pending[i])) { + break; + } + } + if (i == num_pending) { + /* Dump unexpected reply */ + return NT_STATUS_RETRY; + } + + oplock_break = false; + + if (mid == 0xffff) { + /* + * Paranoia checks that this is really an oplock break request. + */ + oplock_break = (len == 51); /* hdr + 8 words */ + oplock_break &= ((CVAL(inhdr, HDR_FLG) & FLAG_REPLY) == 0); + oplock_break &= (CVAL(inhdr, HDR_COM) == SMBlockingX); + oplock_break &= (SVAL(inhdr, HDR_VWV+VWV(6)) == 0); + oplock_break &= (SVAL(inhdr, HDR_VWV+VWV(7)) == 0); + + if (!oplock_break) { + /* Dump unexpected reply */ + return NT_STATUS_RETRY; + } + } + + req = conn->pending[i]; + state = tevent_req_data(req, struct smbXcli_req_state); + + if (!oplock_break /* oplock breaks are not signed */ + && !smb1_signing_check_pdu(conn->smb1.signing, + inhdr, len, state->smb1.seqnum+1)) { + DEBUG(10, ("cli_check_sign_mac failed\n")); + return NT_STATUS_ACCESS_DENIED; + } + + status = smb1cli_inbuf_parse_chain(inbuf, tmp_mem, + &iov, &num_iov); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10,("smb1cli_inbuf_parse_chain - %s\n", + nt_errstr(status))); + return status; + } + + cmd = CVAL(inhdr, HDR_COM); + status = smb1cli_pull_raw_error(inhdr); + + if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED) && + (state->session != NULL) && state->session->disconnect_expired) + { + /* + * this should be a short term hack + * until the upper layers have implemented + * re-authentication. + */ + return status; + } + + if (state->smb1.chained_requests == NULL) { + if (num_iov != 3) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + smbXcli_req_unset_pending(req); + + if (state->smb1.recv_iov == NULL) { + /* + * For requests with more than + * one response, we have to readd the + * recv_iov array. + */ + state->smb1.recv_iov = talloc_zero_array(state, + struct iovec, + 3); + if (tevent_req_nomem(state->smb1.recv_iov, req)) { + return NT_STATUS_OK; + } + } + + state->smb1.recv_cmd = cmd; + state->smb1.recv_status = status; + state->inbuf = talloc_move(state->smb1.recv_iov, &inbuf); + + state->smb1.recv_iov[0] = iov[0]; + state->smb1.recv_iov[1] = iov[1]; + state->smb1.recv_iov[2] = iov[2]; + + if (talloc_array_length(conn->pending) == 0) { + tevent_req_done(req); + return NT_STATUS_OK; + } + + tevent_req_defer_callback(req, state->ev); + tevent_req_done(req); + return NT_STATUS_RETRY; + } + + chain = talloc_move(tmp_mem, &state->smb1.chained_requests); + num_chained = talloc_array_length(chain); + num_responses = (num_iov - 1)/2; + + if (num_responses > num_chained) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + for (i=0; i<num_chained; i++) { + size_t iov_idx = 1 + (i*2); + struct iovec *cur = &iov[iov_idx]; + uint8_t *inbuf_ref; + + req = chain[i]; + state = tevent_req_data(req, struct smbXcli_req_state); + + smbXcli_req_unset_pending(req); + + /* + * as we finish multiple requests here + * we need to defer the callbacks as + * they could destroy our current stack state. + */ + tevent_req_defer_callback(req, state->ev); + + if (i >= num_responses) { + tevent_req_nterror(req, NT_STATUS_REQUEST_ABORTED); + continue; + } + + if (state->smb1.recv_iov == NULL) { + /* + * For requests with more than + * one response, we have to readd the + * recv_iov array. + */ + state->smb1.recv_iov = talloc_zero_array(state, + struct iovec, + 3); + if (tevent_req_nomem(state->smb1.recv_iov, req)) { + continue; + } + } + + state->smb1.recv_cmd = cmd; + + if (i == (num_responses - 1)) { + /* + * The last request in the chain gets the status + */ + state->smb1.recv_status = status; + } else { + cmd = CVAL(cur[0].iov_base, 0); + state->smb1.recv_status = NT_STATUS_OK; + } + + state->inbuf = inbuf; + + /* + * Note: here we use talloc_reference() in a way + * that does not expose it to the caller. + */ + inbuf_ref = talloc_reference(state->smb1.recv_iov, inbuf); + if (tevent_req_nomem(inbuf_ref, req)) { + continue; + } + + /* copy the related buffers */ + state->smb1.recv_iov[0] = iov[0]; + state->smb1.recv_iov[1] = cur[0]; + state->smb1.recv_iov[2] = cur[1]; + + tevent_req_done(req); + } + + return NT_STATUS_RETRY; +} + +NTSTATUS smb1cli_req_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct iovec **piov, + uint8_t **phdr, + uint8_t *pwct, + uint16_t **pvwv, + uint32_t *pvwv_offset, + uint32_t *pnum_bytes, + uint8_t **pbytes, + uint32_t *pbytes_offset, + uint8_t **pinbuf, + const struct smb1cli_req_expected_response *expected, + size_t num_expected) +{ + struct smbXcli_req_state *state = + tevent_req_data(req, + struct smbXcli_req_state); + NTSTATUS status = NT_STATUS_OK; + struct iovec *recv_iov = NULL; + uint8_t *hdr = NULL; + uint8_t wct = 0; + uint32_t vwv_offset = 0; + uint16_t *vwv = NULL; + uint32_t num_bytes = 0; + uint32_t bytes_offset = 0; + uint8_t *bytes = NULL; + size_t i; + bool found_status = false; + bool found_size = false; + + if (piov != NULL) { + *piov = NULL; + } + if (phdr != NULL) { + *phdr = 0; + } + if (pwct != NULL) { + *pwct = 0; + } + if (pvwv != NULL) { + *pvwv = NULL; + } + if (pvwv_offset != NULL) { + *pvwv_offset = 0; + } + if (pnum_bytes != NULL) { + *pnum_bytes = 0; + } + if (pbytes != NULL) { + *pbytes = NULL; + } + if (pbytes_offset != NULL) { + *pbytes_offset = 0; + } + if (pinbuf != NULL) { + *pinbuf = NULL; + } + + if (state->inbuf != NULL) { + recv_iov = state->smb1.recv_iov; + state->smb1.recv_iov = NULL; + if (state->smb1.recv_cmd != SMBreadBraw) { + hdr = (uint8_t *)recv_iov[0].iov_base; + wct = recv_iov[1].iov_len/2; + vwv = (uint16_t *)recv_iov[1].iov_base; + vwv_offset = PTR_DIFF(vwv, hdr); + num_bytes = recv_iov[2].iov_len; + bytes = (uint8_t *)recv_iov[2].iov_base; + bytes_offset = PTR_DIFF(bytes, hdr); + } + } + + if (tevent_req_is_nterror(req, &status)) { + for (i=0; i < num_expected; i++) { + if (NT_STATUS_EQUAL(status, expected[i].status)) { + found_status = true; + break; + } + } + + if (found_status) { + return NT_STATUS_UNEXPECTED_NETWORK_ERROR; + } + + return status; + } + + if (num_expected == 0) { + found_status = true; + found_size = true; + } + + status = state->smb1.recv_status; + + for (i=0; i < num_expected; i++) { + if (!NT_STATUS_EQUAL(status, expected[i].status)) { + continue; + } + + found_status = true; + if (expected[i].wct == 0) { + found_size = true; + break; + } + + if (expected[i].wct == wct) { + found_size = true; + break; + } + } + + if (!found_status) { + return status; + } + + if (!found_size) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + if (piov != NULL) { + *piov = talloc_move(mem_ctx, &recv_iov); + } + + if (phdr != NULL) { + *phdr = hdr; + } + if (pwct != NULL) { + *pwct = wct; + } + if (pvwv != NULL) { + *pvwv = vwv; + } + if (pvwv_offset != NULL) { + *pvwv_offset = vwv_offset; + } + if (pnum_bytes != NULL) { + *pnum_bytes = num_bytes; + } + if (pbytes != NULL) { + *pbytes = bytes; + } + if (pbytes_offset != NULL) { + *pbytes_offset = bytes_offset; + } + if (pinbuf != NULL) { + *pinbuf = state->inbuf; + } + + return status; +} + +size_t smb1cli_req_wct_ofs(struct tevent_req **reqs, int num_reqs) +{ + size_t wct_ofs; + int i; + + wct_ofs = HDR_WCT; + + for (i=0; i<num_reqs; i++) { + struct smbXcli_req_state *state; + state = tevent_req_data(reqs[i], struct smbXcli_req_state); + wct_ofs += smbXcli_iov_len(state->smb1.iov+2, + state->smb1.iov_count-2); + wct_ofs = (wct_ofs + 3) & ~3; + } + return wct_ofs; +} + +NTSTATUS smb1cli_req_chain_submit(struct tevent_req **reqs, int num_reqs) +{ + struct smbXcli_req_state *first_state = + tevent_req_data(reqs[0], + struct smbXcli_req_state); + struct smbXcli_req_state *state; + size_t wct_offset; + size_t chain_padding = 0; + int i, iovlen; + struct iovec *iov = NULL; + struct iovec *this_iov; + NTSTATUS status; + ssize_t nbt_len; + + if (num_reqs == 1) { + return smb1cli_req_writev_submit(reqs[0], first_state, + first_state->smb1.iov, + first_state->smb1.iov_count); + } + + iovlen = 0; + for (i=0; i<num_reqs; i++) { + if (!tevent_req_is_in_progress(reqs[i])) { + return NT_STATUS_INTERNAL_ERROR; + } + + state = tevent_req_data(reqs[i], struct smbXcli_req_state); + + if (state->smb1.iov_count < 4) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + if (i == 0) { + /* + * The NBT and SMB header + */ + iovlen += 2; + } else { + /* + * Chain padding + */ + iovlen += 1; + } + + /* + * words and bytes + */ + iovlen += state->smb1.iov_count - 2; + } + + iov = talloc_zero_array(first_state, struct iovec, iovlen); + if (iov == NULL) { + return NT_STATUS_NO_MEMORY; + } + + first_state->smb1.chained_requests = (struct tevent_req **)talloc_memdup( + first_state, reqs, sizeof(*reqs) * num_reqs); + if (first_state->smb1.chained_requests == NULL) { + TALLOC_FREE(iov); + return NT_STATUS_NO_MEMORY; + } + + wct_offset = HDR_WCT; + this_iov = iov; + + for (i=0; i<num_reqs; i++) { + size_t next_padding = 0; + uint16_t *vwv; + + state = tevent_req_data(reqs[i], struct smbXcli_req_state); + + if (i < num_reqs-1) { + if (!smb1cli_is_andx_req(CVAL(state->smb1.hdr, HDR_COM)) + || CVAL(state->smb1.hdr, HDR_WCT) < 2) { + TALLOC_FREE(iov); + TALLOC_FREE(first_state->smb1.chained_requests); + return NT_STATUS_INVALID_PARAMETER_MIX; + } + } + + wct_offset += smbXcli_iov_len(state->smb1.iov+2, + state->smb1.iov_count-2) + 1; + if ((wct_offset % 4) != 0) { + next_padding = 4 - (wct_offset % 4); + } + wct_offset += next_padding; + vwv = state->smb1.vwv; + + if (i < num_reqs-1) { + struct smbXcli_req_state *next_state = + tevent_req_data(reqs[i+1], + struct smbXcli_req_state); + SCVAL(vwv+0, 0, CVAL(next_state->smb1.hdr, HDR_COM)); + SCVAL(vwv+0, 1, 0); + SSVAL(vwv+1, 0, wct_offset); + } else if (smb1cli_is_andx_req(CVAL(state->smb1.hdr, HDR_COM))) { + /* properly end the chain */ + SCVAL(vwv+0, 0, 0xff); + SCVAL(vwv+0, 1, 0xff); + SSVAL(vwv+1, 0, 0); + } + + if (i == 0) { + /* + * The NBT and SMB header + */ + this_iov[0] = state->smb1.iov[0]; + this_iov[1] = state->smb1.iov[1]; + this_iov += 2; + } else { + /* + * This one is a bit subtle. We have to add + * chain_padding bytes between the requests, and we + * have to also include the wct field of the + * subsequent requests. We use the subsequent header + * for the padding, it contains the wct field in its + * last byte. + */ + this_iov[0].iov_len = chain_padding+1; + this_iov[0].iov_base = (void *)&state->smb1.hdr[ + sizeof(state->smb1.hdr) - this_iov[0].iov_len]; + memset(this_iov[0].iov_base, 0, this_iov[0].iov_len-1); + this_iov += 1; + } + + /* + * copy the words and bytes + */ + memcpy(this_iov, state->smb1.iov+2, + sizeof(struct iovec) * (state->smb1.iov_count-2)); + this_iov += state->smb1.iov_count - 2; + chain_padding = next_padding; + } + + nbt_len = iov_buflen(&iov[1], iovlen-1); + if ((nbt_len == -1) || (nbt_len > first_state->conn->smb1.max_xmit)) { + TALLOC_FREE(iov); + TALLOC_FREE(first_state->smb1.chained_requests); + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + status = smb1cli_req_writev_submit(reqs[0], first_state, iov, iovlen); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(iov); + TALLOC_FREE(first_state->smb1.chained_requests); + return status; + } + + return NT_STATUS_OK; +} + +struct tevent_queue *smbXcli_conn_send_queue(struct smbXcli_conn *conn) +{ + return conn->outgoing; +} + +bool smbXcli_conn_has_async_calls(struct smbXcli_conn *conn) +{ + return ((tevent_queue_length(conn->outgoing) != 0) + || (talloc_array_length(conn->pending) != 0)); +} + +bool smbXcli_conn_dfs_supported(struct smbXcli_conn *conn) +{ + if (conn->protocol >= PROTOCOL_SMB2_02) { + return (smb2cli_conn_server_capabilities(conn) & SMB2_CAP_DFS); + } + + return (smb1cli_conn_capabilities(conn) & CAP_DFS); +} + +bool smb2cli_conn_req_possible(struct smbXcli_conn *conn, uint32_t *max_dyn_len) +{ + uint16_t credits = 1; + + if (conn->smb2.cur_credits == 0) { + if (max_dyn_len != NULL) { + *max_dyn_len = 0; + } + return false; + } + + if (conn->smb2.server.capabilities & SMB2_CAP_LARGE_MTU) { + credits = conn->smb2.cur_credits; + } + + if (max_dyn_len != NULL) { + *max_dyn_len = credits * 65536; + } + + return true; +} + +uint32_t smb2cli_conn_server_capabilities(struct smbXcli_conn *conn) +{ + return conn->smb2.server.capabilities; +} + +uint16_t smb2cli_conn_server_security_mode(struct smbXcli_conn *conn) +{ + return conn->smb2.server.security_mode; +} + +uint16_t smb2cli_conn_server_signing_algo(struct smbXcli_conn *conn) +{ + return conn->smb2.server.sign_algo; +} + +uint16_t smb2cli_conn_server_encryption_algo(struct smbXcli_conn *conn) +{ + return conn->smb2.server.cipher; +} + +uint32_t smb2cli_conn_max_trans_size(struct smbXcli_conn *conn) +{ + return conn->smb2.server.max_trans_size; +} + +uint32_t smb2cli_conn_max_read_size(struct smbXcli_conn *conn) +{ + return conn->smb2.server.max_read_size; +} + +uint32_t smb2cli_conn_max_write_size(struct smbXcli_conn *conn) +{ + return conn->smb2.server.max_write_size; +} + +void smb2cli_conn_set_max_credits(struct smbXcli_conn *conn, + uint16_t max_credits) +{ + conn->smb2.max_credits = max_credits; +} + +uint16_t smb2cli_conn_get_cur_credits(struct smbXcli_conn *conn) +{ + return conn->smb2.cur_credits; +} + +uint8_t smb2cli_conn_get_io_priority(struct smbXcli_conn *conn) +{ + if (conn->protocol < PROTOCOL_SMB3_11) { + return 0; + } + + return conn->smb2.io_priority; +} + +void smb2cli_conn_set_io_priority(struct smbXcli_conn *conn, + uint8_t io_priority) +{ + conn->smb2.io_priority = io_priority; +} + +uint32_t smb2cli_conn_cc_chunk_len(struct smbXcli_conn *conn) +{ + return conn->smb2.cc_chunk_len; +} + +void smb2cli_conn_set_cc_chunk_len(struct smbXcli_conn *conn, + uint32_t chunk_len) +{ + conn->smb2.cc_chunk_len = chunk_len; +} + +uint32_t smb2cli_conn_cc_max_chunks(struct smbXcli_conn *conn) +{ + return conn->smb2.cc_max_chunks; +} + +void smb2cli_conn_set_cc_max_chunks(struct smbXcli_conn *conn, + uint32_t max_chunks) +{ + conn->smb2.cc_max_chunks = max_chunks; +} + +static void smb2cli_req_cancel_done(struct tevent_req *subreq); + +static bool smb2cli_req_cancel(struct tevent_req *req) +{ + struct smbXcli_req_state *state = + tevent_req_data(req, + struct smbXcli_req_state); + struct smbXcli_tcon *tcon = state->tcon; + struct smbXcli_session *session = state->session; + uint8_t *fixed = state->smb2.pad; + uint16_t fixed_len = 4; + struct tevent_req *subreq; + struct smbXcli_req_state *substate; + NTSTATUS status; + + if (state->smb2.cancel_mid == UINT64_MAX) { + /* + * We already send a cancel, + * make sure we don't do it + * twice, otherwise we may + * expose the same NONCE for + * AES-128-GMAC signing + */ + return true; + } + + SSVAL(fixed, 0, 0x04); + SSVAL(fixed, 2, 0); + + subreq = smb2cli_req_create(state, state->ev, + state->conn, + SMB2_OP_CANCEL, + 0, 0, /* flags */ + 0, /* timeout */ + tcon, session, + fixed, fixed_len, + NULL, 0, 0); + if (subreq == NULL) { + return false; + } + substate = tevent_req_data(subreq, struct smbXcli_req_state); + + substate->smb2.cancel_mid = BVAL(state->smb2.hdr, SMB2_HDR_MESSAGE_ID); + + SIVAL(substate->smb2.hdr, SMB2_HDR_FLAGS, state->smb2.cancel_flags); + SBVAL(substate->smb2.hdr, SMB2_HDR_MESSAGE_ID, state->smb2.cancel_mid); + SBVAL(substate->smb2.hdr, SMB2_HDR_ASYNC_ID, state->smb2.cancel_aid); + + /* + * remember that we don't send a cancel again + */ + state->smb2.cancel_mid = UINT64_MAX; + + status = smb2cli_req_compound_submit(&subreq, 1); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(subreq); + return false; + } + + tevent_req_set_callback(subreq, smb2cli_req_cancel_done, NULL); + + return true; +} + +static void smb2cli_req_cancel_done(struct tevent_req *subreq) +{ + /* we do not care about the result */ + TALLOC_FREE(subreq); +} + +struct timeval smbXcli_req_endtime(struct tevent_req *req) +{ + struct smbXcli_req_state *state = tevent_req_data( + req, struct smbXcli_req_state); + + return state->endtime; +} + +struct tevent_req *smb2cli_req_create(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint16_t cmd, + uint32_t additional_flags, + uint32_t clear_flags, + uint32_t timeout_msec, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + const uint8_t *fixed, + uint16_t fixed_len, + const uint8_t *dyn, + uint32_t dyn_len, + uint32_t max_dyn_len) +{ + struct tevent_req *req; + struct smbXcli_req_state *state; + uint32_t flags = 0; + uint32_t tid = 0; + uint64_t uid = 0; + bool use_channel_sequence = conn->smb2.force_channel_sequence; + uint16_t channel_sequence = 0; + bool use_replay_flag = false; + + req = tevent_req_create(mem_ctx, &state, + struct smbXcli_req_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->conn = conn; + state->session = session; + state->tcon = tcon; + + if (conn->smb2.server.capabilities & SMB2_CAP_PERSISTENT_HANDLES) { + use_channel_sequence = true; + } else if (conn->smb2.server.capabilities & SMB2_CAP_MULTI_CHANNEL) { + use_channel_sequence = true; + } + + if (smbXcli_conn_protocol(conn) >= PROTOCOL_SMB3_00) { + use_replay_flag = true; + } + + if (smbXcli_conn_protocol(conn) >= PROTOCOL_SMB3_11) { + flags |= SMB2_PRIORITY_VALUE_TO_MASK(conn->smb2.io_priority); + } + + if (session) { + uid = session->smb2->session_id; + + if (use_channel_sequence) { + channel_sequence = session->smb2->channel_sequence; + } + + if (use_replay_flag && session->smb2->replay_active) { + additional_flags |= SMB2_HDR_FLAG_REPLAY_OPERATION; + } + + state->smb2.should_sign = session->smb2->should_sign; + state->smb2.should_encrypt = session->smb2->should_encrypt; + state->smb2.require_signed_response = + session->smb2->require_signed_response; + + if (cmd == SMB2_OP_SESSSETUP && + !smb2_signing_key_valid(session->smb2_channel.signing_key) && + smb2_signing_key_valid(session->smb2->signing_key)) + { + /* + * a session bind needs to be signed + */ + state->smb2.should_sign = true; + } + + if (cmd == SMB2_OP_SESSSETUP && + !smb2_signing_key_valid(session->smb2_channel.signing_key)) { + state->smb2.should_encrypt = false; + } + + if (additional_flags & SMB2_HDR_FLAG_SIGNED) { + if (!smb2_signing_key_valid(session->smb2_channel.signing_key)) { + tevent_req_nterror(req, NT_STATUS_NO_USER_SESSION_KEY); + return req; + } + + additional_flags &= ~SMB2_HDR_FLAG_SIGNED; + state->smb2.should_sign = true; + } + } + + if (tcon) { + tid = tcon->smb2.tcon_id; + + if (tcon->smb2.should_sign) { + state->smb2.should_sign = true; + } + if (tcon->smb2.should_encrypt) { + state->smb2.should_encrypt = true; + } + } + + if (state->smb2.should_encrypt) { + state->smb2.should_sign = false; + } + + state->smb2.recv_iov = talloc_zero_array(state, struct iovec, 3); + if (state->smb2.recv_iov == NULL) { + TALLOC_FREE(req); + return NULL; + } + + flags |= additional_flags; + flags &= ~clear_flags; + + state->smb2.fixed = fixed; + state->smb2.fixed_len = fixed_len; + state->smb2.dyn = dyn; + state->smb2.dyn_len = dyn_len; + state->smb2.max_dyn_len = max_dyn_len; + + if (state->smb2.should_encrypt) { + SIVAL(state->smb2.transform, SMB2_TF_PROTOCOL_ID, SMB2_TF_MAGIC); + SBVAL(state->smb2.transform, SMB2_TF_SESSION_ID, uid); + } + + SIVAL(state->smb2.hdr, SMB2_HDR_PROTOCOL_ID, SMB2_MAGIC); + SSVAL(state->smb2.hdr, SMB2_HDR_LENGTH, SMB2_HDR_BODY); + SSVAL(state->smb2.hdr, SMB2_HDR_OPCODE, cmd); + SSVAL(state->smb2.hdr, SMB2_HDR_CHANNEL_SEQUENCE, channel_sequence); + SIVAL(state->smb2.hdr, SMB2_HDR_FLAGS, flags); + SIVAL(state->smb2.hdr, SMB2_HDR_PID, 0); /* reserved */ + SIVAL(state->smb2.hdr, SMB2_HDR_TID, tid); + SBVAL(state->smb2.hdr, SMB2_HDR_SESSION_ID, uid); + + switch (cmd) { + case SMB2_OP_CANCEL: + state->one_way = true; + break; + case SMB2_OP_BREAK: + /* + * If this is a dummy request, it will have + * UINT64_MAX as message id. + * If we send on break acknowledgement, + * this gets overwritten later. + */ + SBVAL(state->smb2.hdr, SMB2_HDR_MESSAGE_ID, UINT64_MAX); + break; + } + + if (timeout_msec > 0) { + state->endtime = timeval_current_ofs_msec(timeout_msec); + if (!tevent_req_set_endtime(req, ev, state->endtime)) { + return req; + } + } + + return req; +} + +void smb2cli_req_set_notify_async(struct tevent_req *req) +{ + struct smbXcli_req_state *state = + tevent_req_data(req, + struct smbXcli_req_state); + + state->smb2.notify_async = true; +} + +static void smb2cli_req_writev_done(struct tevent_req *subreq); +static NTSTATUS smb2cli_conn_dispatch_incoming(struct smbXcli_conn *conn, + TALLOC_CTX *tmp_mem, + uint8_t *inbuf); + +NTSTATUS smb2cli_req_compound_submit(struct tevent_req **reqs, + int num_reqs) +{ + struct smbXcli_req_state *state; + struct tevent_req *subreq; + struct iovec *iov; + int i, num_iov, nbt_len; + int tf_iov = -1; + struct smb2_signing_key *encryption_key = NULL; + uint64_t encryption_session_id = 0; + uint64_t nonce_high = UINT64_MAX; + uint64_t nonce_low = UINT64_MAX; + + /* + * 1 for the nbt length, optional TRANSFORM + * per request: HDR, fixed, dyn, padding + * -1 because the last one does not need padding + */ + + iov = talloc_array(reqs[0], struct iovec, 1 + 1 + 4*num_reqs - 1); + if (iov == NULL) { + return NT_STATUS_NO_MEMORY; + } + + num_iov = 1; + nbt_len = 0; + + /* + * the session of the first request that requires encryption + * specifies the encryption key. + */ + for (i=0; i<num_reqs; i++) { + if (!tevent_req_is_in_progress(reqs[i])) { + return NT_STATUS_INTERNAL_ERROR; + } + + state = tevent_req_data(reqs[i], struct smbXcli_req_state); + + if (!smbXcli_conn_is_connected(state->conn)) { + return NT_STATUS_CONNECTION_DISCONNECTED; + } + + if ((state->conn->protocol != PROTOCOL_NONE) && + (state->conn->protocol < PROTOCOL_SMB2_02)) { + return NT_STATUS_REVISION_MISMATCH; + } + + if (state->session == NULL) { + continue; + } + + if (!state->smb2.should_encrypt) { + continue; + } + + encryption_key = state->session->smb2->encryption_key; + if (!smb2_signing_key_valid(encryption_key)) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + encryption_session_id = state->session->smb2->session_id; + + state->session->smb2->nonce_low += 1; + if (state->session->smb2->nonce_low == 0) { + state->session->smb2->nonce_high += 1; + state->session->smb2->nonce_low += 1; + } + + /* + * CCM and GCM algorithms must never have their + * nonce wrap, or the security of the whole + * communication and the keys is destroyed. + * We must drop the connection once we have + * transfered too much data. + * + * NOTE: We assume nonces greater than 8 bytes. + */ + if (state->session->smb2->nonce_high >= + state->session->smb2->nonce_high_max) + { + return NT_STATUS_ENCRYPTION_FAILED; + } + + nonce_high = state->session->smb2->nonce_high_random; + nonce_high += state->session->smb2->nonce_high; + nonce_low = state->session->smb2->nonce_low; + + tf_iov = num_iov; + iov[num_iov].iov_base = state->smb2.transform; + iov[num_iov].iov_len = sizeof(state->smb2.transform); + num_iov += 1; + + SBVAL(state->smb2.transform, SMB2_TF_PROTOCOL_ID, SMB2_TF_MAGIC); + SBVAL(state->smb2.transform, SMB2_TF_NONCE, + nonce_low); + SBVAL(state->smb2.transform, SMB2_TF_NONCE+8, + nonce_high); + SBVAL(state->smb2.transform, SMB2_TF_SESSION_ID, + encryption_session_id); + + nbt_len += SMB2_TF_HDR_SIZE; + break; + } + + for (i=0; i<num_reqs; i++) { + int hdr_iov; + size_t reqlen; + bool ret; + uint16_t opcode; + uint64_t avail; + uint16_t charge; + uint16_t credits; + uint64_t mid; + struct smb2_signing_key *signing_key = NULL; + + if (!tevent_req_is_in_progress(reqs[i])) { + return NT_STATUS_INTERNAL_ERROR; + } + + state = tevent_req_data(reqs[i], struct smbXcli_req_state); + + if (!smbXcli_conn_is_connected(state->conn)) { + return NT_STATUS_CONNECTION_DISCONNECTED; + } + + if ((state->conn->protocol != PROTOCOL_NONE) && + (state->conn->protocol < PROTOCOL_SMB2_02)) { + return NT_STATUS_REVISION_MISMATCH; + } + + opcode = SVAL(state->smb2.hdr, SMB2_HDR_OPCODE); + if (opcode == SMB2_OP_CANCEL) { + goto skip_credits; + } + + avail = UINT64_MAX - state->conn->smb2.mid; + if (avail < 1) { + return NT_STATUS_CONNECTION_ABORTED; + } + + if (state->conn->smb2.server.capabilities & SMB2_CAP_LARGE_MTU) { + uint32_t max_dyn_len = 1; + + max_dyn_len = MAX(max_dyn_len, state->smb2.dyn_len); + max_dyn_len = MAX(max_dyn_len, state->smb2.max_dyn_len); + + charge = (max_dyn_len - 1)/ 65536 + 1; + } else { + charge = 1; + } + + charge = MAX(state->smb2.credit_charge, charge); + + avail = MIN(avail, state->conn->smb2.cur_credits); + if (avail < charge) { + DBG_ERR("Insufficient credits. " + "%"PRIu64" available, %"PRIu16" needed\n", + avail, charge); + return NT_STATUS_INTERNAL_ERROR; + } + + credits = 0; + if (state->conn->smb2.max_credits > state->conn->smb2.cur_credits) { + credits = state->conn->smb2.max_credits - + state->conn->smb2.cur_credits; + } + if (state->conn->smb2.max_credits >= state->conn->smb2.cur_credits) { + credits += 1; + } + + mid = state->conn->smb2.mid; + state->conn->smb2.mid += charge; + state->conn->smb2.cur_credits -= charge; + + if (state->conn->smb2.server.capabilities & SMB2_CAP_LARGE_MTU) { + SSVAL(state->smb2.hdr, SMB2_HDR_CREDIT_CHARGE, charge); + } + SSVAL(state->smb2.hdr, SMB2_HDR_CREDIT, credits); + SBVAL(state->smb2.hdr, SMB2_HDR_MESSAGE_ID, mid); + + state->smb2.cancel_flags = SVAL(state->smb2.hdr, SMB2_HDR_FLAGS); + state->smb2.cancel_flags &= ~SMB2_HDR_FLAG_CHAINED; + if (state->conn->smb2.server.sign_algo >= SMB2_SIGNING_AES128_GMAC) { + state->smb2.cancel_mid = mid; + } else { + state->smb2.cancel_mid = 0; + } + state->smb2.cancel_aid = 0; + +skip_credits: + if (state->session && encryption_key == NULL) { + /* + * We prefer the channel signing key if it is + * already there. + */ + if (state->smb2.should_sign) { + signing_key = state->session->smb2_channel.signing_key; + } + + /* + * If it is a channel binding, we already have the main + * signing key and try that one. + */ + if (signing_key != NULL && + !smb2_signing_key_valid(signing_key)) { + signing_key = state->session->smb2->signing_key; + } + + /* + * If we do not have any session key yet, we skip the + * signing of SMB2_OP_SESSSETUP requests. + */ + if (signing_key != NULL && + !smb2_signing_key_valid(signing_key)) { + signing_key = NULL; + } + } + + hdr_iov = num_iov; + iov[num_iov].iov_base = state->smb2.hdr; + iov[num_iov].iov_len = sizeof(state->smb2.hdr); + num_iov += 1; + + iov[num_iov].iov_base = discard_const(state->smb2.fixed); + iov[num_iov].iov_len = state->smb2.fixed_len; + num_iov += 1; + + if (state->smb2.dyn != NULL) { + iov[num_iov].iov_base = discard_const(state->smb2.dyn); + iov[num_iov].iov_len = state->smb2.dyn_len; + num_iov += 1; + } + + reqlen = sizeof(state->smb2.hdr); + reqlen += state->smb2.fixed_len; + reqlen += state->smb2.dyn_len; + + if (i < num_reqs-1) { + if ((reqlen % 8) > 0) { + uint8_t pad = 8 - (reqlen % 8); + iov[num_iov].iov_base = state->smb2.pad; + iov[num_iov].iov_len = pad; + num_iov += 1; + reqlen += pad; + } + SIVAL(state->smb2.hdr, SMB2_HDR_NEXT_COMMAND, reqlen); + } + + state->smb2.encryption_session_id = encryption_session_id; + + if (signing_key != NULL) { + NTSTATUS status; + + status = smb2_signing_sign_pdu(signing_key, + &iov[hdr_iov], num_iov - hdr_iov); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + nbt_len += reqlen; + + ret = smbXcli_req_set_pending(reqs[i]); + if (!ret) { + return NT_STATUS_NO_MEMORY; + } + } + + state = tevent_req_data(reqs[0], struct smbXcli_req_state); + _smb_setlen_tcp(state->length_hdr, nbt_len); + iov[0].iov_base = state->length_hdr; + iov[0].iov_len = sizeof(state->length_hdr); + + if (encryption_key != NULL) { + NTSTATUS status; + size_t buflen = nbt_len - SMB2_TF_HDR_SIZE; + uint8_t *buf; + int vi; + + buf = talloc_array(iov, uint8_t, buflen); + if (buf == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* + * We copy the buffers before encrypting them, + * this is at least currently needed for the + * to keep state->smb2.hdr. + * + * Also the callers may expect there buffers + * to be const. + */ + for (vi = tf_iov + 1; vi < num_iov; vi++) { + struct iovec *v = &iov[vi]; + const uint8_t *o = (const uint8_t *)v->iov_base; + + memcpy(buf, o, v->iov_len); + v->iov_base = (void *)buf; + buf += v->iov_len; + } + + status = smb2_signing_encrypt_pdu(encryption_key, + &iov[tf_iov], num_iov - tf_iov); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + if (state->conn->dispatch_incoming == NULL) { + state->conn->dispatch_incoming = smb2cli_conn_dispatch_incoming; + } + + subreq = writev_send(state, state->ev, state->conn->outgoing, + state->conn->sock_fd, false, iov, num_iov); + if (subreq == NULL) { + return NT_STATUS_NO_MEMORY; + } + tevent_req_set_callback(subreq, smb2cli_req_writev_done, reqs[0]); + state->write_req = subreq; + + return NT_STATUS_OK; +} + +void smb2cli_req_set_credit_charge(struct tevent_req *req, uint16_t charge) +{ + struct smbXcli_req_state *state = + tevent_req_data(req, + struct smbXcli_req_state); + + state->smb2.credit_charge = charge; +} + +struct tevent_req *smb2cli_req_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint16_t cmd, + uint32_t additional_flags, + uint32_t clear_flags, + uint32_t timeout_msec, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + const uint8_t *fixed, + uint16_t fixed_len, + const uint8_t *dyn, + uint32_t dyn_len, + uint32_t max_dyn_len) +{ + struct tevent_req *req; + NTSTATUS status; + + req = smb2cli_req_create(mem_ctx, ev, conn, cmd, + additional_flags, clear_flags, + timeout_msec, + tcon, session, + fixed, fixed_len, + dyn, dyn_len, + max_dyn_len); + if (req == NULL) { + return NULL; + } + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + status = smb2cli_req_compound_submit(&req, 1); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + return req; +} + +static void smb2cli_req_writev_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct smbXcli_req_state *state = + tevent_req_data(req, + struct smbXcli_req_state); + ssize_t nwritten; + int err; + + state->write_req = NULL; + + nwritten = writev_recv(subreq, &err); + TALLOC_FREE(subreq); + if (nwritten == -1) { + /* here, we need to notify all pending requests */ + NTSTATUS status = map_nt_error_from_unix_common(err); + smbXcli_conn_disconnect(state->conn, status); + return; + } +} + +static struct smbXcli_session* smbXcli_session_by_uid(struct smbXcli_conn *conn, + uint64_t uid) +{ + struct smbXcli_session *s = conn->sessions; + + for (; s; s = s->next) { + if (s->smb2->session_id != uid) { + continue; + } + break; + } + + return s; +} + +static NTSTATUS smb2cli_inbuf_parse_compound(struct smbXcli_conn *conn, + uint8_t *buf, + size_t buflen, + TALLOC_CTX *mem_ctx, + struct iovec **piov, + size_t *pnum_iov) +{ + struct iovec *iov; + int num_iov = 0; + size_t taken = 0; + uint8_t *first_hdr = buf; + size_t verified_buflen = 0; + uint8_t *tf = NULL; + size_t tf_len = 0; + + iov = talloc_array(mem_ctx, struct iovec, num_iov); + if (iov == NULL) { + return NT_STATUS_NO_MEMORY; + } + + while (taken < buflen) { + size_t len = buflen - taken; + uint8_t *hdr = first_hdr + taken; + struct iovec *cur; + size_t full_size; + size_t next_command_ofs; + uint16_t body_size; + struct iovec *iov_tmp; + + if (verified_buflen > taken) { + len = verified_buflen - taken; + } else { + tf = NULL; + tf_len = 0; + } + + if (len < 4) { + DEBUG(10, ("%d bytes left, expected at least %d\n", + (int)len, 4)); + goto inval; + } + if (IVAL(hdr, 0) == SMB2_TF_MAGIC) { + struct smbXcli_session *s; + uint64_t uid; + struct iovec tf_iov[2]; + size_t enc_len; + NTSTATUS status; + + if (len < SMB2_TF_HDR_SIZE) { + DEBUG(10, ("%d bytes left, expected at least %d\n", + (int)len, SMB2_TF_HDR_SIZE)); + goto inval; + } + tf = hdr; + tf_len = SMB2_TF_HDR_SIZE; + taken += tf_len; + + hdr = first_hdr + taken; + enc_len = IVAL(tf, SMB2_TF_MSG_SIZE); + uid = BVAL(tf, SMB2_TF_SESSION_ID); + + if (len < SMB2_TF_HDR_SIZE + enc_len) { + DEBUG(10, ("%d bytes left, expected at least %d\n", + (int)len, + (int)(SMB2_TF_HDR_SIZE + enc_len))); + goto inval; + } + + s = smbXcli_session_by_uid(conn, uid); + if (s == NULL) { + DEBUG(10, ("unknown session_id %llu\n", + (unsigned long long)uid)); + goto inval; + } + + tf_iov[0].iov_base = (void *)tf; + tf_iov[0].iov_len = tf_len; + tf_iov[1].iov_base = (void *)hdr; + tf_iov[1].iov_len = enc_len; + + status = smb2_signing_decrypt_pdu(s->smb2->decryption_key, + tf_iov, 2); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(iov); + return status; + } + + verified_buflen = taken + enc_len; + len = enc_len; + } + + /* + * We need the header plus the body length field + */ + + if (len < SMB2_HDR_BODY + 2) { + DEBUG(10, ("%d bytes left, expected at least %d\n", + (int)len, SMB2_HDR_BODY)); + goto inval; + } + if (IVAL(hdr, 0) != SMB2_MAGIC) { + DEBUG(10, ("Got non-SMB2 PDU: %x\n", + IVAL(hdr, 0))); + goto inval; + } + if (SVAL(hdr, 4) != SMB2_HDR_BODY) { + DEBUG(10, ("Got HDR len %d, expected %d\n", + SVAL(hdr, 4), SMB2_HDR_BODY)); + goto inval; + } + + full_size = len; + next_command_ofs = IVAL(hdr, SMB2_HDR_NEXT_COMMAND); + body_size = SVAL(hdr, SMB2_HDR_BODY); + + if (next_command_ofs != 0) { + if (next_command_ofs < (SMB2_HDR_BODY + 2)) { + goto inval; + } + if (next_command_ofs > full_size) { + goto inval; + } + full_size = next_command_ofs; + } + if (body_size < 2) { + goto inval; + } + body_size &= 0xfffe; + + if (body_size > (full_size - SMB2_HDR_BODY)) { + goto inval; + } + + iov_tmp = talloc_realloc(mem_ctx, iov, struct iovec, + num_iov + 4); + if (iov_tmp == NULL) { + TALLOC_FREE(iov); + return NT_STATUS_NO_MEMORY; + } + iov = iov_tmp; + cur = &iov[num_iov]; + num_iov += 4; + + cur[0].iov_base = tf; + cur[0].iov_len = tf_len; + cur[1].iov_base = hdr; + cur[1].iov_len = SMB2_HDR_BODY; + cur[2].iov_base = hdr + SMB2_HDR_BODY; + cur[2].iov_len = body_size; + cur[3].iov_base = hdr + SMB2_HDR_BODY + body_size; + cur[3].iov_len = full_size - (SMB2_HDR_BODY + body_size); + + taken += full_size; + } + + *piov = iov; + *pnum_iov = num_iov; + return NT_STATUS_OK; + +inval: + TALLOC_FREE(iov); + return NT_STATUS_INVALID_NETWORK_RESPONSE; +} + +static struct tevent_req *smb2cli_conn_find_pending(struct smbXcli_conn *conn, + uint64_t mid) +{ + size_t num_pending = talloc_array_length(conn->pending); + size_t i; + + for (i=0; i<num_pending; i++) { + struct tevent_req *req = conn->pending[i]; + struct smbXcli_req_state *state = + tevent_req_data(req, + struct smbXcli_req_state); + + if (mid == BVAL(state->smb2.hdr, SMB2_HDR_MESSAGE_ID)) { + return req; + } + } + return NULL; +} + +static NTSTATUS smb2cli_conn_dispatch_incoming(struct smbXcli_conn *conn, + TALLOC_CTX *tmp_mem, + uint8_t *inbuf) +{ + struct tevent_req *req; + struct smbXcli_req_state *state = NULL; + struct iovec *iov = NULL; + size_t i, num_iov = 0; + NTSTATUS status; + bool defer = true; + struct smbXcli_session *last_session = NULL; + size_t inbuf_len = smb_len_tcp(inbuf); + + status = smb2cli_inbuf_parse_compound(conn, + inbuf + NBT_HDR_SIZE, + inbuf_len, + tmp_mem, + &iov, &num_iov); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + for (i=0; i<num_iov; i+=4) { + uint8_t *inbuf_ref = NULL; + struct iovec *cur = &iov[i]; + uint8_t *inhdr = (uint8_t *)cur[1].iov_base; + uint16_t opcode = SVAL(inhdr, SMB2_HDR_OPCODE); + uint32_t flags = IVAL(inhdr, SMB2_HDR_FLAGS); + uint64_t mid = BVAL(inhdr, SMB2_HDR_MESSAGE_ID); + uint16_t req_opcode; + uint32_t req_flags; + uint16_t credits = SVAL(inhdr, SMB2_HDR_CREDIT); + uint32_t new_credits; + struct smbXcli_session *session = NULL; + struct smb2_signing_key *signing_key = NULL; + bool was_encrypted = false; + + new_credits = conn->smb2.cur_credits; + new_credits += credits; + if (new_credits > UINT16_MAX) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + conn->smb2.cur_credits += credits; + + req = smb2cli_conn_find_pending(conn, mid); + if (req == NULL) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + state = tevent_req_data(req, struct smbXcli_req_state); + + req_opcode = SVAL(state->smb2.hdr, SMB2_HDR_OPCODE); + if (opcode != req_opcode) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + req_flags = SVAL(state->smb2.hdr, SMB2_HDR_FLAGS); + + if (!(flags & SMB2_HDR_FLAG_REDIRECT)) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + status = NT_STATUS(IVAL(inhdr, SMB2_HDR_STATUS)); + if ((flags & SMB2_HDR_FLAG_ASYNC) && + NT_STATUS_EQUAL(status, NT_STATUS_PENDING)) { + uint64_t async_id = BVAL(inhdr, SMB2_HDR_ASYNC_ID); + + if (state->smb2.got_async) { + /* We only expect one STATUS_PENDING response */ + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + state->smb2.got_async = true; + + /* + * async interim responses are not signed, + * even if the SMB2_HDR_FLAG_SIGNED flag + * is set. + */ + state->smb2.cancel_flags |= SMB2_HDR_FLAG_ASYNC; + state->smb2.cancel_aid = async_id; + + if (state->smb2.notify_async) { + tevent_req_defer_callback(req, state->ev); + tevent_req_notify_callback(req); + } + continue; + } + + session = state->session; + if (req_flags & SMB2_HDR_FLAG_CHAINED) { + session = last_session; + } + last_session = session; + + if (flags & SMB2_HDR_FLAG_SIGNED) { + uint64_t uid = BVAL(inhdr, SMB2_HDR_SESSION_ID); + + if (session == NULL) { + session = smbXcli_session_by_uid(state->conn, + uid); + } + + if (session == NULL) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + last_session = session; + signing_key = session->smb2_channel.signing_key; + } + + if (opcode == SMB2_OP_SESSSETUP) { + /* + * We prefer the channel signing key, if it is + * already there. + * + * If we do not have a channel signing key yet, + * we try the main signing key, if it is not + * the final response. + */ + if (signing_key != NULL && + !smb2_signing_key_valid(signing_key) && + !NT_STATUS_IS_OK(status)) { + signing_key = session->smb2->signing_key; + } + + if (signing_key != NULL && + !smb2_signing_key_valid(signing_key)) { + /* + * If we do not have a session key to + * verify the signature, we defer the + * signing check to the caller. + * + * The caller gets NT_STATUS_OK, it + * has to call + * smb2cli_session_set_session_key() + * or + * smb2cli_session_set_channel_key() + * which will check the signature + * with the channel signing key. + */ + signing_key = NULL; + } + + if (!NT_STATUS_IS_OK(status)) { + /* + * Only check the signature of the last response + * of a successfull session auth. This matches + * Windows behaviour for NTLM auth and reauth. + */ + state->smb2.require_signed_response = false; + } + } + + if (state->smb2.should_sign || + state->smb2.require_signed_response) + { + if (!(flags & SMB2_HDR_FLAG_SIGNED)) { + return NT_STATUS_ACCESS_DENIED; + } + } + + if (!smb2_signing_key_valid(signing_key) && + state->smb2.require_signed_response) { + signing_key = session->smb2_channel.signing_key; + } + + if (cur[0].iov_len == SMB2_TF_HDR_SIZE) { + const uint8_t *tf = (const uint8_t *)cur[0].iov_base; + uint64_t uid = BVAL(tf, SMB2_TF_SESSION_ID); + + /* + * If the response was encrypted in a SMB2_TRANSFORM + * pdu, which belongs to the correct session, + * we do not need to do signing checks + * + * It could be the session the response belongs to + * or the session that was used to encrypt the + * SMB2_TRANSFORM request. + */ + if ((session && session->smb2->session_id == uid) || + (state->smb2.encryption_session_id == uid)) { + signing_key = NULL; + was_encrypted = true; + } + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_USER_SESSION_DELETED)) { + /* + * if the server returns NT_STATUS_USER_SESSION_DELETED + * the response is not signed and we should + * propagate the NT_STATUS_USER_SESSION_DELETED + * status to the caller. + */ + state->smb2.signing_skipped = true; + signing_key = NULL; + } + if (NT_STATUS_EQUAL(status, NT_STATUS_REQUEST_OUT_OF_SEQUENCE)) { + /* + * if the server returns + * NT_STATUS_REQUEST_OUT_OF_SEQUENCE for a session setup + * request, the response is not signed and we should + * propagate the NT_STATUS_REQUEST_OUT_OF_SEQUENCE + * status to the caller + */ + if (opcode == SMB2_OP_SESSSETUP) { + state->smb2.signing_skipped = true; + signing_key = NULL; + } + } + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) { + /* + * if the server returns NT_STATUS_NOT_SUPPORTED + * for a session setup request, the response is not + * signed and we should propagate the NT_STATUS_NOT_SUPPORTED + * status to the caller. + */ + if (opcode == SMB2_OP_SESSSETUP) { + state->smb2.signing_skipped = true; + signing_key = NULL; + } + } + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + /* + * if the server returns + * NT_STATUS_ACCESS_DENIED for a session setup + * request, the response is not signed and we should + * propagate the NT_STATUS_ACCESS_DENIED + * status to the caller without disconnecting + * the connection because we where not able to + * verify the response signature. + */ + if (opcode == SMB2_OP_SESSSETUP) { + state->smb2.signing_skipped = true; + signing_key = NULL; + } + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) { + /* + * if the server returns + * NT_STATUS_INVALID_PARAMETER + * the response might not be encrypted. + */ + if (state->smb2.should_encrypt && !was_encrypted) { + state->smb2.signing_skipped = true; + signing_key = NULL; + } + } + + if (state->smb2.should_encrypt && !was_encrypted) { + if (!state->smb2.signing_skipped) { + return NT_STATUS_ACCESS_DENIED; + } + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_NAME_DELETED) || + NT_STATUS_EQUAL(status, NT_STATUS_FILE_CLOSED) || + NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) { + /* + * if the server returns + * NT_STATUS_NETWORK_NAME_DELETED + * NT_STATUS_FILE_CLOSED + * NT_STATUS_INVALID_PARAMETER + * the response might not be signed + * as this happens before the signing checks. + * + * If server echos the signature (or all zeros) + * we should report the status from the server + * to the caller. + */ + if (signing_key) { + bool cmp; + + cmp = mem_equal_const_time(inhdr+SMB2_HDR_SIGNATURE, + state->smb2.hdr+SMB2_HDR_SIGNATURE, + 16); + if (cmp) { + state->smb2.signing_skipped = true; + signing_key = NULL; + } + } + if (signing_key) { + bool zero; + zero = all_zero(inhdr+SMB2_HDR_SIGNATURE, 16); + if (zero) { + state->smb2.signing_skipped = true; + signing_key = NULL; + } + } + } + + if (signing_key) { + NTSTATUS signing_status; + + signing_status = smb2_signing_check_pdu(signing_key, + &cur[1], 3); + if (!NT_STATUS_IS_OK(signing_status)) { + /* + * If the signing check fails, we disconnect + * the connection. + */ + return signing_status; + } + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED) && + (session != NULL) && session->disconnect_expired) + { + /* + * this should be a short term hack + * until the upper layers have implemented + * re-authentication. + */ + return status; + } + + smbXcli_req_unset_pending(req); + + /* + * There might be more than one response + * we need to defer the notifications + */ + if ((num_iov == 5) && (talloc_array_length(conn->pending) == 0)) { + defer = false; + } + + if (defer) { + tevent_req_defer_callback(req, state->ev); + } + + /* + * Note: here we use talloc_reference() in a way + * that does not expose it to the caller. + */ + inbuf_ref = talloc_reference(state->smb2.recv_iov, inbuf); + if (tevent_req_nomem(inbuf_ref, req)) { + continue; + } + + /* copy the related buffers */ + state->smb2.recv_iov[0] = cur[1]; + state->smb2.recv_iov[1] = cur[2]; + state->smb2.recv_iov[2] = cur[3]; + + tevent_req_done(req); + } + + if (defer) { + return NT_STATUS_RETRY; + } + + return NT_STATUS_OK; +} + +NTSTATUS smb2cli_req_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct iovec **piov, + const struct smb2cli_req_expected_response *expected, + size_t num_expected) +{ + struct smbXcli_req_state *state = + tevent_req_data(req, + struct smbXcli_req_state); + NTSTATUS status; + size_t body_size; + bool found_status = false; + bool found_size = false; + size_t i; + + if (piov != NULL) { + *piov = NULL; + } + + if (tevent_req_is_in_progress(req) && state->smb2.got_async) { + return NT_STATUS_PENDING; + } + + if (tevent_req_is_nterror(req, &status)) { + for (i=0; i < num_expected; i++) { + if (NT_STATUS_EQUAL(status, expected[i].status)) { + return NT_STATUS_UNEXPECTED_NETWORK_ERROR; + } + } + + return status; + } + + if (num_expected == 0) { + found_status = true; + found_size = true; + } + + status = NT_STATUS(IVAL(state->smb2.recv_iov[0].iov_base, SMB2_HDR_STATUS)); + body_size = SVAL(state->smb2.recv_iov[1].iov_base, 0); + + for (i=0; i < num_expected; i++) { + if (!NT_STATUS_EQUAL(status, expected[i].status)) { + continue; + } + + found_status = true; + if (expected[i].body_size == 0) { + found_size = true; + break; + } + + if (expected[i].body_size == body_size) { + found_size = true; + break; + } + } + + if (!found_status) { + return status; + } + + if (state->smb2.signing_skipped) { + if (num_expected > 0) { + return NT_STATUS_ACCESS_DENIED; + } + if (!NT_STATUS_IS_ERR(status)) { + return NT_STATUS_ACCESS_DENIED; + } + } + + if (!found_size) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + if (piov != NULL) { + *piov = talloc_move(mem_ctx, &state->smb2.recv_iov); + } + + return status; +} + +NTSTATUS smb2cli_req_get_sent_iov(struct tevent_req *req, + struct iovec *sent_iov) +{ + struct smbXcli_req_state *state = + tevent_req_data(req, + struct smbXcli_req_state); + + if (tevent_req_is_in_progress(req)) { + return NT_STATUS_PENDING; + } + + sent_iov[0].iov_base = state->smb2.hdr; + sent_iov[0].iov_len = sizeof(state->smb2.hdr); + + sent_iov[1].iov_base = discard_const(state->smb2.fixed); + sent_iov[1].iov_len = state->smb2.fixed_len; + + if (state->smb2.dyn != NULL) { + sent_iov[2].iov_base = discard_const(state->smb2.dyn); + sent_iov[2].iov_len = state->smb2.dyn_len; + } else { + sent_iov[2].iov_base = NULL; + sent_iov[2].iov_len = 0; + } + + return NT_STATUS_OK; +} + +static const struct { + enum protocol_types proto; + const char *smb1_name; +} smb1cli_prots[] = { + {PROTOCOL_CORE, "PC NETWORK PROGRAM 1.0"}, + {PROTOCOL_COREPLUS, "MICROSOFT NETWORKS 1.03"}, + {PROTOCOL_LANMAN1, "MICROSOFT NETWORKS 3.0"}, + {PROTOCOL_LANMAN1, "LANMAN1.0"}, + {PROTOCOL_LANMAN2, "LM1.2X002"}, + {PROTOCOL_LANMAN2, "DOS LANMAN2.1"}, + {PROTOCOL_LANMAN2, "LANMAN2.1"}, + {PROTOCOL_LANMAN2, "Samba"}, + {PROTOCOL_NT1, "NT LANMAN 1.0"}, + {PROTOCOL_NT1, "NT LM 0.12"}, + {PROTOCOL_SMB2_02, "SMB 2.002"}, + {PROTOCOL_SMB2_10, "SMB 2.???"}, +}; + +static const struct { + enum protocol_types proto; + uint16_t smb2_dialect; +} smb2cli_prots[] = { + {PROTOCOL_SMB2_02, SMB2_DIALECT_REVISION_202}, + {PROTOCOL_SMB2_10, SMB2_DIALECT_REVISION_210}, + {PROTOCOL_SMB3_00, SMB3_DIALECT_REVISION_300}, + {PROTOCOL_SMB3_02, SMB3_DIALECT_REVISION_302}, + {PROTOCOL_SMB3_11, SMB3_DIALECT_REVISION_311}, +}; + +struct smbXcli_negprot_state { + struct smbXcli_conn *conn; + struct tevent_context *ev; + struct smb2_negotiate_contexts *in_ctx; + struct smb2_negotiate_contexts *out_ctx; + uint32_t timeout_msec; + + struct { + uint8_t fixed[36]; + } smb2; +}; + +static void smbXcli_negprot_invalid_done(struct tevent_req *subreq); +static struct tevent_req *smbXcli_negprot_smb1_subreq(struct smbXcli_negprot_state *state); +static void smbXcli_negprot_smb1_done(struct tevent_req *subreq); +static struct tevent_req *smbXcli_negprot_smb2_subreq(struct smbXcli_negprot_state *state); +static void smbXcli_negprot_smb2_done(struct tevent_req *subreq); +static NTSTATUS smbXcli_negprot_dispatch_incoming(struct smbXcli_conn *conn, + TALLOC_CTX *frame, + uint8_t *inbuf); + +struct tevent_req *smbXcli_negprot_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + enum protocol_types min_protocol, + enum protocol_types max_protocol, + uint16_t max_credits, + struct smb2_negotiate_contexts *in_ctx) +{ + struct tevent_req *req, *subreq; + struct smbXcli_negprot_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct smbXcli_negprot_state); + if (req == NULL) { + return NULL; + } + state->conn = conn; + state->ev = ev; + state->in_ctx = in_ctx; + state->timeout_msec = timeout_msec; + + if (min_protocol == PROTOCOL_NONE) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + + if (max_protocol == PROTOCOL_NONE) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + + if (min_protocol > max_protocol) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + + conn->min_protocol = min_protocol; + conn->max_protocol = max_protocol; + conn->protocol = PROTOCOL_NONE; + + if (max_protocol >= PROTOCOL_SMB2_02) { + conn->smb2.max_credits = max_credits; + } + + if ((min_protocol < PROTOCOL_SMB2_02) && + (max_protocol < PROTOCOL_SMB2_02)) { + /* + * SMB1 only... + */ + conn->dispatch_incoming = smb1cli_conn_dispatch_incoming; + + subreq = smbXcli_negprot_smb1_subreq(state); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smbXcli_negprot_smb1_done, req); + return req; + } + + if ((min_protocol >= PROTOCOL_SMB2_02) && + (max_protocol >= PROTOCOL_SMB2_02)) { + /* + * SMB2 only... + */ + conn->dispatch_incoming = smb2cli_conn_dispatch_incoming; + + subreq = smbXcli_negprot_smb2_subreq(state); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smbXcli_negprot_smb2_done, req); + return req; + } + + /* + * We send an SMB1 negprot with the SMB2 dialects + * and expect a SMB1 or a SMB2 response. + * + * smbXcli_negprot_dispatch_incoming() will fix the + * callback to match protocol of the response. + */ + conn->dispatch_incoming = smbXcli_negprot_dispatch_incoming; + + subreq = smbXcli_negprot_smb1_subreq(state); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smbXcli_negprot_invalid_done, req); + return req; +} + +static void smbXcli_negprot_invalid_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + NTSTATUS status; + + /* + * we just want the low level error + */ + status = tevent_req_simple_recv_ntstatus(subreq); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + /* this should never happen */ + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); +} + +static struct tevent_req *smbXcli_negprot_smb1_subreq(struct smbXcli_negprot_state *state) +{ + size_t i; + DATA_BLOB bytes = data_blob_null; + uint8_t flags; + uint16_t flags2; + + /* setup the protocol strings */ + for (i=0; i < ARRAY_SIZE(smb1cli_prots); i++) { + uint8_t c = 2; + bool ok; + + if (smb1cli_prots[i].proto < state->conn->min_protocol) { + continue; + } + + if (smb1cli_prots[i].proto > state->conn->max_protocol) { + continue; + } + + ok = data_blob_append(state, &bytes, &c, sizeof(c)); + if (!ok) { + return NULL; + } + + /* + * We now it is already ascii and + * we want NULL termination. + */ + ok = data_blob_append(state, &bytes, + smb1cli_prots[i].smb1_name, + strlen(smb1cli_prots[i].smb1_name)+1); + if (!ok) { + return NULL; + } + } + + smb1cli_req_flags(state->conn->max_protocol, + state->conn->smb1.client.capabilities, + SMBnegprot, + 0, 0, &flags, + 0, 0, &flags2); + + return smb1cli_req_send(state, state->ev, state->conn, + SMBnegprot, + flags, ~flags, + flags2, ~flags2, + state->timeout_msec, + 0xFFFE, 0, NULL, /* pid, tid, session */ + 0, NULL, /* wct, vwv */ + bytes.length, bytes.data); +} + +static void smbXcli_negprot_smb1_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct smbXcli_negprot_state *state = + tevent_req_data(req, + struct smbXcli_negprot_state); + struct smbXcli_conn *conn = state->conn; + struct iovec *recv_iov = NULL; + uint8_t *inhdr = NULL; + uint8_t wct; + uint16_t *vwv; + uint32_t num_bytes; + uint8_t *bytes; + NTSTATUS status; + uint16_t protnum; + size_t i; + size_t num_prots = 0; + uint8_t flags; + uint32_t client_capabilities = conn->smb1.client.capabilities; + uint32_t both_capabilities; + uint32_t server_capabilities = 0; + uint32_t capabilities; + uint32_t client_max_xmit = conn->smb1.client.max_xmit; + uint32_t server_max_xmit = 0; + uint32_t max_xmit; + uint32_t server_max_mux = 0; + uint16_t server_security_mode = 0; + uint32_t server_session_key = 0; + bool server_readbraw = false; + bool server_writebraw = false; + bool server_lockread = false; + bool server_writeunlock = false; + struct GUID server_guid = GUID_zero(); + DATA_BLOB server_gss_blob = data_blob_null; + uint8_t server_challenge[8]; + char *server_workgroup = NULL; + char *server_name = NULL; + int server_time_zone = 0; + NTTIME server_system_time = 0; + static const struct smb1cli_req_expected_response expected[] = { + { + .status = NT_STATUS_OK, + .wct = 0x11, /* NT1 */ + }, + { + .status = NT_STATUS_OK, + .wct = 0x0D, /* LM */ + }, + { + .status = NT_STATUS_OK, + .wct = 0x01, /* CORE */ + } + }; + + ZERO_STRUCT(server_challenge); + + status = smb1cli_req_recv(subreq, state, + &recv_iov, + &inhdr, + &wct, + &vwv, + NULL, /* pvwv_offset */ + &num_bytes, + &bytes, + NULL, /* pbytes_offset */ + NULL, /* pinbuf */ + expected, ARRAY_SIZE(expected)); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + if (inhdr == NULL) { + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + return; + } + + flags = CVAL(inhdr, HDR_FLG); + + protnum = SVAL(vwv, 0); + + for (i=0; i < ARRAY_SIZE(smb1cli_prots); i++) { + if (smb1cli_prots[i].proto < state->conn->min_protocol) { + continue; + } + + if (smb1cli_prots[i].proto > state->conn->max_protocol) { + continue; + } + + if (protnum != num_prots) { + num_prots++; + continue; + } + + conn->protocol = smb1cli_prots[i].proto; + break; + } + + if (conn->protocol == PROTOCOL_NONE) { + DBG_ERR("No compatible protocol selected by server.\n"); + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + if ((conn->protocol < PROTOCOL_NT1) && conn->mandatory_signing) { + DEBUG(0,("smbXcli_negprot: SMB signing is mandatory " + "and the selected protocol level doesn't support it.\n")); + tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED); + return; + } + + if (flags & FLAG_SUPPORT_LOCKREAD) { + server_lockread = true; + server_writeunlock = true; + } + + if (conn->protocol >= PROTOCOL_NT1) { + const char *client_signing = NULL; + bool server_mandatory = false; + bool server_allowed = false; + const char *server_signing = NULL; + bool ok; + uint8_t key_len; + + if (wct != 0x11) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + /* NT protocol */ + server_security_mode = CVAL(vwv + 1, 0); + server_max_mux = SVAL(vwv + 1, 1); + server_max_xmit = IVAL(vwv + 3, 1); + server_session_key = IVAL(vwv + 7, 1); + server_time_zone = SVALS(vwv + 15, 1); + server_time_zone *= 60; + /* this time arrives in real GMT */ + server_system_time = BVAL(vwv + 11, 1); + server_capabilities = IVAL(vwv + 9, 1); + + key_len = CVAL(vwv + 16, 1); + + if (server_capabilities & CAP_RAW_MODE) { + server_readbraw = true; + server_writebraw = true; + } + if (server_capabilities & CAP_LOCK_AND_READ) { + server_lockread = true; + } + + if (server_capabilities & CAP_EXTENDED_SECURITY) { + DATA_BLOB blob1, blob2; + + if (num_bytes < 16) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + blob1 = data_blob_const(bytes, 16); + status = GUID_from_data_blob(&blob1, &server_guid); + if (tevent_req_nterror(req, status)) { + return; + } + + blob1 = data_blob_const(bytes+16, num_bytes-16); + blob2 = data_blob_dup_talloc(state, blob1); + if (blob1.length > 0 && + tevent_req_nomem(blob2.data, req)) { + return; + } + server_gss_blob = blob2; + } else { + DATA_BLOB blob1, blob2; + + if (num_bytes < key_len) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + if (key_len != 0 && key_len != 8) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + if (key_len == 8) { + memcpy(server_challenge, bytes, 8); + } + + blob1 = data_blob_const(bytes+key_len, num_bytes-key_len); + blob2 = data_blob_const(bytes+key_len, num_bytes-key_len); + if (blob1.length > 0) { + size_t len; + + len = utf16_len_n(blob1.data, + blob1.length); + blob1.length = len; + + ok = convert_string_talloc(state, + CH_UTF16LE, + CH_UNIX, + blob1.data, + blob1.length, + &server_workgroup, + &len); + if (!ok) { + status = map_nt_error_from_unix_common(errno); + tevent_req_nterror(req, status); + return; + } + } + + blob2.data += blob1.length; + blob2.length -= blob1.length; + if (blob2.length > 0) { + size_t len; + + len = utf16_len_n(blob1.data, + blob1.length); + blob1.length = len; + + ok = convert_string_talloc(state, + CH_UTF16LE, + CH_UNIX, + blob2.data, + blob2.length, + &server_name, + &len); + if (!ok) { + status = map_nt_error_from_unix_common(errno); + tevent_req_nterror(req, status); + return; + } + } + } + + client_signing = "disabled"; + if (conn->allow_signing) { + client_signing = "allowed"; + } + if (conn->mandatory_signing) { + client_signing = "required"; + } + + server_signing = "not supported"; + if (server_security_mode & NEGOTIATE_SECURITY_SIGNATURES_ENABLED) { + server_signing = "supported"; + server_allowed = true; + } else if (conn->mandatory_signing) { + /* + * We have mandatory signing as client + * lets assume the server will look at our + * FLAGS2_SMB_SECURITY_SIGNATURES_REQUIRED + * flag in the session setup + */ + server_signing = "not announced"; + server_allowed = true; + } + if (server_security_mode & NEGOTIATE_SECURITY_SIGNATURES_REQUIRED) { + server_signing = "required"; + server_mandatory = true; + } + + ok = smb1_signing_set_negotiated(conn->smb1.signing, + server_allowed, + server_mandatory); + if (!ok) { + DEBUG(1,("cli_negprot: SMB signing is required, " + "but client[%s] and server[%s] mismatch\n", + client_signing, server_signing)); + tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED); + return; + } + + } else if (conn->protocol >= PROTOCOL_LANMAN1) { + DATA_BLOB blob1; + uint8_t key_len; + time_t t; + + if (wct != 0x0D) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + server_security_mode = SVAL(vwv + 1, 0); + server_max_xmit = SVAL(vwv + 2, 0); + server_max_mux = SVAL(vwv + 3, 0); + server_readbraw = ((SVAL(vwv + 5, 0) & 0x1) != 0); + server_writebraw = ((SVAL(vwv + 5, 0) & 0x2) != 0); + server_session_key = IVAL(vwv + 6, 0); + server_time_zone = SVALS(vwv + 10, 0); + server_time_zone *= 60; + /* this time is converted to GMT by make_unix_date */ + t = pull_dos_date((const uint8_t *)(vwv + 8), server_time_zone); + unix_to_nt_time(&server_system_time, t); + key_len = SVAL(vwv + 11, 0); + + if (num_bytes < key_len) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + if (key_len != 0 && key_len != 8) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + if (key_len == 8) { + memcpy(server_challenge, bytes, 8); + } + + blob1 = data_blob_const(bytes+key_len, num_bytes-key_len); + if (blob1.length > 0) { + size_t len; + bool ok; + + len = utf16_len_n(blob1.data, + blob1.length); + blob1.length = len; + + ok = convert_string_talloc(state, + CH_DOS, + CH_UNIX, + blob1.data, + blob1.length, + &server_workgroup, + &len); + if (!ok) { + status = map_nt_error_from_unix_common(errno); + tevent_req_nterror(req, status); + return; + } + } + + } else { + /* the old core protocol */ + server_time_zone = get_time_zone(time(NULL)); + server_max_xmit = 1024; + server_max_mux = 1; + } + + if (server_max_xmit < 1024) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + if (server_max_mux < 1) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + /* + * Now calculate the negotiated capabilities + * based on the mask for: + * - client only flags + * - flags used in both directions + * - server only flags + */ + both_capabilities = client_capabilities & server_capabilities; + capabilities = client_capabilities & SMB_CAP_CLIENT_MASK; + capabilities |= both_capabilities & SMB_CAP_BOTH_MASK; + capabilities |= server_capabilities & SMB_CAP_SERVER_MASK; + + max_xmit = MIN(client_max_xmit, server_max_xmit); + + conn->smb1.server.capabilities = server_capabilities; + conn->smb1.capabilities = capabilities; + + conn->smb1.server.max_xmit = server_max_xmit; + conn->smb1.max_xmit = max_xmit; + + conn->smb1.server.max_mux = server_max_mux; + + conn->smb1.server.security_mode = server_security_mode; + + conn->smb1.server.readbraw = server_readbraw; + conn->smb1.server.writebraw = server_writebraw; + conn->smb1.server.lockread = server_lockread; + conn->smb1.server.writeunlock = server_writeunlock; + + conn->smb1.server.session_key = server_session_key; + + talloc_steal(conn, server_gss_blob.data); + conn->smb1.server.gss_blob = server_gss_blob; + conn->smb1.server.guid = server_guid; + memcpy(conn->smb1.server.challenge, server_challenge, 8); + conn->smb1.server.workgroup = talloc_move(conn, &server_workgroup); + conn->smb1.server.name = talloc_move(conn, &server_name); + + conn->smb1.server.time_zone = server_time_zone; + conn->smb1.server.system_time = server_system_time; + + tevent_req_done(req); +} + +static size_t smbXcli_padding_helper(uint32_t offset, size_t n) +{ + if ((offset & (n-1)) == 0) return 0; + return n - (offset & (n-1)); +} + +static struct tevent_req *smbXcli_negprot_smb2_subreq(struct smbXcli_negprot_state *state) +{ + size_t i; + uint8_t *buf; + uint16_t dialect_count = 0; + DATA_BLOB dyn = data_blob_null; + + for (i=0; i < ARRAY_SIZE(smb2cli_prots); i++) { + bool ok; + uint8_t val[2]; + + if (smb2cli_prots[i].proto < state->conn->min_protocol) { + continue; + } + + if (smb2cli_prots[i].proto > state->conn->max_protocol) { + continue; + } + + SSVAL(val, 0, smb2cli_prots[i].smb2_dialect); + + ok = data_blob_append(state, &dyn, val, sizeof(val)); + if (!ok) { + return NULL; + } + + dialect_count++; + } + + buf = state->smb2.fixed; + SSVAL(buf, 0, 36); + SSVAL(buf, 2, dialect_count); + SSVAL(buf, 4, state->conn->smb2.client.security_mode); + SSVAL(buf, 6, 0); /* Reserved */ + if (state->conn->max_protocol >= PROTOCOL_SMB3_00) { + SIVAL(buf, 8, state->conn->smb2.client.capabilities); + } else { + SIVAL(buf, 8, 0); /* Capabilities */ + } + if (state->conn->max_protocol >= PROTOCOL_SMB2_10) { + NTSTATUS status; + struct GUID_ndr_buf guid_buf = { .buf = {0}, }; + + status = GUID_to_ndr_buf(&state->conn->smb2.client.guid, + &guid_buf); + if (!NT_STATUS_IS_OK(status)) { + return NULL; + } + memcpy(buf+12, guid_buf.buf, 16); /* ClientGuid */ + } else { + memset(buf+12, 0, 16); /* ClientGuid */ + } + + if (state->conn->max_protocol >= PROTOCOL_SMB3_11) { + const struct smb3_signing_capabilities *client_sign_algos = + &state->conn->smb2.client.smb3_capabilities.signing; + const struct smb3_encryption_capabilities *client_ciphers = + &state->conn->smb2.client.smb3_capabilities.encryption; + NTSTATUS status; + struct smb2_negotiate_contexts c = { .num_contexts = 0, }; + uint8_t *netname_utf16 = NULL; + size_t netname_utf16_len = 0; + uint32_t offset; + DATA_BLOB b; + uint8_t p[38]; + const uint8_t zeros[8] = {0, }; + size_t pad; + bool ok; + + SSVAL(p, 0, 1); /* HashAlgorithmCount */ + SSVAL(p, 2, 32); /* SaltLength */ + SSVAL(p, 4, SMB2_PREAUTH_INTEGRITY_SHA512); + generate_random_buffer(p + 6, 32); + + status = smb2_negotiate_context_add( + state, &c, SMB2_PREAUTH_INTEGRITY_CAPABILITIES, p, 38); + if (!NT_STATUS_IS_OK(status)) { + return NULL; + } + + if (client_ciphers->num_algos > 0) { + size_t ofs = 0; + SSVAL(p, ofs, client_ciphers->num_algos); + ofs += 2; + + for (i = 0; i < client_ciphers->num_algos; i++) { + size_t next_ofs = ofs + 2; + SMB_ASSERT(next_ofs < ARRAY_SIZE(p)); + SSVAL(p, ofs, client_ciphers->algos[i]); + ofs = next_ofs; + } + + status = smb2_negotiate_context_add( + state, &c, SMB2_ENCRYPTION_CAPABILITIES, p, ofs); + if (!NT_STATUS_IS_OK(status)) { + return NULL; + } + } + + if (client_sign_algos->num_algos > 0) { + size_t ofs = 0; + SSVAL(p, ofs, client_sign_algos->num_algos); + ofs += 2; + + for (i = 0; i < client_sign_algos->num_algos; i++) { + size_t next_ofs = ofs + 2; + SMB_ASSERT(next_ofs < ARRAY_SIZE(p)); + SSVAL(p, ofs, client_sign_algos->algos[i]); + ofs = next_ofs; + } + + status = smb2_negotiate_context_add( + state, &c, SMB2_SIGNING_CAPABILITIES, p, ofs); + if (!NT_STATUS_IS_OK(status)) { + return NULL; + } + } + + ok = convert_string_talloc(state, CH_UNIX, CH_UTF16, + state->conn->remote_name, + strlen(state->conn->remote_name), + &netname_utf16, &netname_utf16_len); + if (!ok) { + return NULL; + } + + status = smb2_negotiate_context_add(state, &c, + SMB2_NETNAME_NEGOTIATE_CONTEXT_ID, + netname_utf16, netname_utf16_len); + if (!NT_STATUS_IS_OK(status)) { + return NULL; + } + + if (state->in_ctx != NULL) { + struct smb2_negotiate_contexts *ctxs = state->in_ctx; + + for (i=0; i<ctxs->num_contexts; i++) { + struct smb2_negotiate_context *ctx = + &ctxs->contexts[i]; + + status = smb2_negotiate_context_add( + state, + &c, + ctx->type, + ctx->data.data, + ctx->data.length); + if (!NT_STATUS_IS_OK(status)) { + return NULL; + } + } + } + + status = smb2_negotiate_context_push(state, &b, c); + if (!NT_STATUS_IS_OK(status)) { + return NULL; + } + + offset = SMB2_HDR_BODY + sizeof(state->smb2.fixed) + dyn.length; + pad = smbXcli_padding_helper(offset, 8); + + ok = data_blob_append(state, &dyn, zeros, pad); + if (!ok) { + return NULL; + } + offset += pad; + + ok = data_blob_append(state, &dyn, b.data, b.length); + if (!ok) { + return NULL; + } + + SIVAL(buf, 28, offset); /* NegotiateContextOffset */ + SSVAL(buf, 32, c.num_contexts); /* NegotiateContextCount */ + SSVAL(buf, 34, 0); /* Reserved */ + } else { + SBVAL(buf, 28, 0); /* Reserved/ClientStartTime */ + } + + return smb2cli_req_send(state, state->ev, + state->conn, SMB2_OP_NEGPROT, + 0, 0, /* flags */ + state->timeout_msec, + NULL, NULL, /* tcon, session */ + state->smb2.fixed, sizeof(state->smb2.fixed), + dyn.data, dyn.length, + UINT16_MAX); /* max_dyn_len */ +} + +static NTSTATUS smbXcli_negprot_smb3_check_capabilities(struct tevent_req *req); + +static void smbXcli_negprot_smb2_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct smbXcli_negprot_state *state = + tevent_req_data(req, + struct smbXcli_negprot_state); + struct smbXcli_conn *conn = state->conn; + size_t security_offset, security_length; + DATA_BLOB blob; + NTSTATUS status; + struct iovec *iov = NULL; + uint8_t *body; + size_t i; + uint16_t dialect_revision; + uint32_t negotiate_context_offset = 0; + uint16_t negotiate_context_count = 0; + DATA_BLOB negotiate_context_blob = data_blob_null; + size_t avail; + size_t ctx_ofs; + size_t needed; + struct smb2_negotiate_context *preauth = NULL; + uint16_t hash_count; + uint16_t salt_length; + uint16_t hash_selected; + gnutls_hash_hd_t hash_hnd = NULL; + struct smb2_negotiate_context *sign_algo = NULL; + struct smb2_negotiate_context *cipher = NULL; + struct iovec sent_iov[3] = {{0}, {0}, {0}}; + static const struct smb2cli_req_expected_response expected[] = { + { + .status = NT_STATUS_OK, + .body_size = 0x41 + } + }; + int rc; + + status = smb2cli_req_recv(subreq, state, &iov, + expected, ARRAY_SIZE(expected)); + if (tevent_req_nterror(req, status)) { + return; + } + if (iov == NULL) { + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + return; + } + + body = (uint8_t *)iov[1].iov_base; + + dialect_revision = SVAL(body, 4); + + for (i=0; i < ARRAY_SIZE(smb2cli_prots); i++) { + if (smb2cli_prots[i].proto < state->conn->min_protocol) { + continue; + } + + if (smb2cli_prots[i].proto > state->conn->max_protocol) { + continue; + } + + if (smb2cli_prots[i].smb2_dialect != dialect_revision) { + continue; + } + + conn->protocol = smb2cli_prots[i].proto; + break; + } + + if (conn->protocol == PROTOCOL_NONE) { + TALLOC_FREE(subreq); + + if (state->conn->min_protocol >= PROTOCOL_SMB2_02) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + if (dialect_revision != SMB2_DIALECT_REVISION_2FF) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + /* make sure we do not loop forever */ + state->conn->min_protocol = PROTOCOL_SMB2_02; + + /* + * send a SMB2 negprot, in order to negotiate + * the SMB2 dialect. + */ + subreq = smbXcli_negprot_smb2_subreq(state); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, smbXcli_negprot_smb2_done, req); + return; + } + + conn->smb2.server.security_mode = SVAL(body, 2); + if (conn->protocol >= PROTOCOL_SMB3_11) { + negotiate_context_count = SVAL(body, 6); + } + + blob = data_blob_const(body + 8, 16); + status = GUID_from_data_blob(&blob, &conn->smb2.server.guid); + if (tevent_req_nterror(req, status)) { + return; + } + + conn->smb2.server.capabilities = IVAL(body, 24); + conn->smb2.server.max_trans_size= IVAL(body, 28); + conn->smb2.server.max_read_size = IVAL(body, 32); + conn->smb2.server.max_write_size= IVAL(body, 36); + conn->smb2.server.system_time = BVAL(body, 40); + conn->smb2.server.start_time = BVAL(body, 48); + + if (conn->smb2.server.max_trans_size == 0 || + conn->smb2.server.max_read_size == 0 || + conn->smb2.server.max_write_size == 0) { + /* + * We can't connect to servers we can't + * do any operations on. + */ + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + security_offset = SVAL(body, 56); + security_length = SVAL(body, 58); + + if (security_offset == 0) { + /* + * Azure sends security_offset = 0 and security_length = 0 + * + * We just set security_offset to the expected value + * in order to allow the further logic to work + * as before. + */ + if (security_length != 0) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + security_offset = SMB2_HDR_BODY + iov[1].iov_len; + } + + if (security_offset != SMB2_HDR_BODY + iov[1].iov_len) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + if (security_length > iov[2].iov_len) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + conn->smb2.server.gss_blob = data_blob_talloc(conn, + iov[2].iov_base, + security_length); + if (tevent_req_nomem(conn->smb2.server.gss_blob.data, req)) { + return; + } + + if (conn->protocol >= PROTOCOL_SMB3_00) { + conn->smb2.server.sign_algo = SMB2_SIGNING_AES128_CMAC; + } else { + conn->smb2.server.sign_algo = SMB2_SIGNING_HMAC_SHA256; + } + + if (conn->protocol < PROTOCOL_SMB3_11) { + TALLOC_FREE(subreq); + + if (conn->smb2.server.capabilities & SMB2_CAP_ENCRYPTION) { + conn->smb2.server.cipher = SMB2_ENCRYPTION_AES128_CCM; + } + + status = smbXcli_negprot_smb3_check_capabilities(req); + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); + return; + } + + /* + * Here we are now at SMB3_11, so encryption should be + * negotiated via context, not capabilities. + */ + + if (conn->smb2.server.capabilities & SMB2_CAP_ENCRYPTION) { + /* + * Server set SMB2_CAP_ENCRYPTION capability, + * but *SHOULD* not, not *MUST* not. Just mask it off. + * NetApp seems to do this: + * BUG: https://bugzilla.samba.org/show_bug.cgi?id=13009 + */ + conn->smb2.server.capabilities &= ~SMB2_CAP_ENCRYPTION; + } + + negotiate_context_offset = IVAL(body, 60); + if (negotiate_context_offset < security_offset) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + ctx_ofs = negotiate_context_offset - security_offset; + if (ctx_ofs > iov[2].iov_len) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + avail = iov[2].iov_len - security_length; + needed = iov[2].iov_len - ctx_ofs; + if (needed > avail) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + negotiate_context_blob.data = (uint8_t *)iov[2].iov_base; + negotiate_context_blob.length = iov[2].iov_len; + + negotiate_context_blob.data += ctx_ofs; + negotiate_context_blob.length -= ctx_ofs; + + state->out_ctx = talloc_zero(state, struct smb2_negotiate_contexts); + if (tevent_req_nomem(state->out_ctx, req)) { + return; + } + + status = smb2_negotiate_context_parse(state->out_ctx, + negotiate_context_blob, + negotiate_context_count, + state->out_ctx); + if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) { + status = NT_STATUS_INVALID_NETWORK_RESPONSE; + } + if (tevent_req_nterror(req, status)) { + return; + } + + preauth = smb2_negotiate_context_find( + state->out_ctx, SMB2_PREAUTH_INTEGRITY_CAPABILITIES); + if (preauth == NULL) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + if (preauth->data.length < 6) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + hash_count = SVAL(preauth->data.data, 0); + salt_length = SVAL(preauth->data.data, 2); + hash_selected = SVAL(preauth->data.data, 4); + + if (hash_count != 1) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + if (preauth->data.length != (6 + salt_length)) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + if (hash_selected != SMB2_PREAUTH_INTEGRITY_SHA512) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + sign_algo = smb2_negotiate_context_find( + state->out_ctx, SMB2_SIGNING_CAPABILITIES); + if (sign_algo != NULL) { + const struct smb3_signing_capabilities *client_sign_algos = + &state->conn->smb2.client.smb3_capabilities.signing; + bool found_selected = false; + uint16_t sign_algo_count; + uint16_t sign_algo_selected; + + if (client_sign_algos->num_algos == 0) { + /* + * We didn't ask for SMB2_ENCRYPTION_CAPABILITIES + */ + tevent_req_nterror(req, + NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + if (sign_algo->data.length < 2) { + tevent_req_nterror(req, + NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + sign_algo_count = SVAL(sign_algo->data.data, 0); + if (sign_algo_count != 1) { + tevent_req_nterror(req, + NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + if (sign_algo->data.length < (2 + 2 * sign_algo_count)) { + tevent_req_nterror(req, + NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + sign_algo_selected = SVAL(sign_algo->data.data, 2); + + for (i = 0; i < client_sign_algos->num_algos; i++) { + if (client_sign_algos->algos[i] == sign_algo_selected) { + /* + * We found a match + */ + found_selected = true; + break; + } + } + + if (!found_selected) { + /* + * The server send a sign_algo we didn't offer. + */ + tevent_req_nterror(req, + NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + conn->smb2.server.sign_algo = sign_algo_selected; + } + + cipher = smb2_negotiate_context_find( + state->out_ctx, SMB2_ENCRYPTION_CAPABILITIES); + if (cipher != NULL) { + const struct smb3_encryption_capabilities *client_ciphers = + &state->conn->smb2.client.smb3_capabilities.encryption; + bool found_selected = false; + uint16_t cipher_count; + uint16_t cipher_selected; + + if (client_ciphers->num_algos == 0) { + /* + * We didn't ask for SMB2_ENCRYPTION_CAPABILITIES + */ + tevent_req_nterror(req, + NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + if (cipher->data.length < 2) { + tevent_req_nterror(req, + NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + cipher_count = SVAL(cipher->data.data, 0); + if (cipher_count != 1) { + tevent_req_nterror(req, + NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + if (cipher->data.length < (2 + 2 * cipher_count)) { + tevent_req_nterror(req, + NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + cipher_selected = SVAL(cipher->data.data, 2); + + for (i = 0; i < client_ciphers->num_algos; i++) { + if (cipher_selected == SMB2_ENCRYPTION_NONE) { + /* + * encryption not supported + */ + found_selected = true; + break; + } + if (client_ciphers->algos[i] == cipher_selected) { + /* + * We found a match + */ + found_selected = true; + break; + } + } + + if (!found_selected) { + /* + * The server send a cipher we didn't offer. + */ + tevent_req_nterror(req, + NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + conn->smb2.server.cipher = cipher_selected; + } + + /* First we hash the request */ + smb2cli_req_get_sent_iov(subreq, sent_iov); + + rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_SHA512); + if (rc < 0) { + tevent_req_nterror(req, + gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED)); + return; + } + + rc = gnutls_hash(hash_hnd, + conn->smb2.preauth_sha512, + sizeof(conn->smb2.preauth_sha512)); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + tevent_req_nterror(req, + gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED)); + return; + } + for (i = 0; i < 3; i++) { + rc = gnutls_hash(hash_hnd, + sent_iov[i].iov_base, + sent_iov[i].iov_len); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + tevent_req_nterror(req, + gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED)); + return; + } + } + + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + tevent_req_nterror(req, + gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED)); + return; + } + + /* This resets the hash state */ + gnutls_hash_output(hash_hnd, conn->smb2.preauth_sha512); + TALLOC_FREE(subreq); + + /* And now we hash the response */ + rc = gnutls_hash(hash_hnd, + conn->smb2.preauth_sha512, + sizeof(conn->smb2.preauth_sha512)); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + tevent_req_nterror(req, + gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED)); + return; + } + for (i = 0; i < 3; i++) { + rc = gnutls_hash(hash_hnd, + iov[i].iov_base, + iov[i].iov_len); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + tevent_req_nterror(req, + gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED)); + return; + } + } + gnutls_hash_deinit(hash_hnd, conn->smb2.preauth_sha512); + if (rc < 0) { + tevent_req_nterror(req, + NT_STATUS_UNSUCCESSFUL); + return; + } + + status = smbXcli_negprot_smb3_check_capabilities(req); + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); +} + +static NTSTATUS smbXcli_negprot_smb3_check_capabilities(struct tevent_req *req) +{ + struct smbXcli_negprot_state *state = + tevent_req_data(req, + struct smbXcli_negprot_state); + struct smbXcli_conn *conn = state->conn; + + return smb311_capabilities_check(&conn->smb2.client.smb3_capabilities, + "smbXcli_negprot", + DBGLVL_ERR, + NT_STATUS_ACCESS_DENIED, + "client", + conn->protocol, + conn->smb2.server.sign_algo, + conn->smb2.server.cipher); +} + +static NTSTATUS smbXcli_negprot_dispatch_incoming(struct smbXcli_conn *conn, + TALLOC_CTX *tmp_mem, + uint8_t *inbuf) +{ + size_t num_pending = talloc_array_length(conn->pending); + struct tevent_req *subreq; + struct smbXcli_req_state *substate; + struct tevent_req *req; + uint32_t protocol_magic; + size_t inbuf_len = smb_len_nbt(inbuf); + + if (num_pending != 1) { + return NT_STATUS_INTERNAL_ERROR; + } + + if (inbuf_len < 4) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + subreq = conn->pending[0]; + substate = tevent_req_data(subreq, struct smbXcli_req_state); + req = tevent_req_callback_data(subreq, struct tevent_req); + + protocol_magic = IVAL(inbuf, 4); + + switch (protocol_magic) { + case SMB_MAGIC: + tevent_req_set_callback(subreq, smbXcli_negprot_smb1_done, req); + conn->dispatch_incoming = smb1cli_conn_dispatch_incoming; + return smb1cli_conn_dispatch_incoming(conn, tmp_mem, inbuf); + + case SMB2_MAGIC: + if (substate->smb2.recv_iov == NULL) { + /* + * For the SMB1 negprot we have move it. + */ + substate->smb2.recv_iov = substate->smb1.recv_iov; + substate->smb1.recv_iov = NULL; + } + + /* + * we got an SMB2 answer, which consumed sequence number 0 + * so we need to use 1 as the next one. + * + * we also need to set the current credits to 0 + * as we consumed the initial one. The SMB2 answer + * hopefully grant us a new credit. + */ + conn->smb2.mid = 1; + conn->smb2.cur_credits = 0; + tevent_req_set_callback(subreq, smbXcli_negprot_smb2_done, req); + conn->dispatch_incoming = smb2cli_conn_dispatch_incoming; + return smb2cli_conn_dispatch_incoming(conn, tmp_mem, inbuf); + } + + DEBUG(10, ("Got non-SMB PDU\n")); + return NT_STATUS_INVALID_NETWORK_RESPONSE; +} + +NTSTATUS smbXcli_negprot_recv( + struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct smb2_negotiate_contexts **out_ctx) +{ + struct smbXcli_negprot_state *state = tevent_req_data( + req, struct smbXcli_negprot_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + if (out_ctx != NULL) { + *out_ctx = talloc_move(mem_ctx, &state->out_ctx); + } + + tevent_req_received(req); + return NT_STATUS_OK; +} + +NTSTATUS smbXcli_negprot(struct smbXcli_conn *conn, + uint32_t timeout_msec, + enum protocol_types min_protocol, + enum protocol_types max_protocol) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + bool ok; + + if (smbXcli_conn_has_async_calls(conn)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER_MIX; + goto fail; + } + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = smbXcli_negprot_send( + frame, + ev, + conn, + timeout_msec, + min_protocol, + max_protocol, + WINDOWS_CLIENT_PURE_SMB2_NEGPROT_INITIAL_CREDIT_ASK, + NULL); + if (req == NULL) { + goto fail; + } + ok = tevent_req_poll_ntstatus(req, ev, &status); + if (!ok) { + goto fail; + } + status = smbXcli_negprot_recv(req, NULL, NULL); + fail: + TALLOC_FREE(frame); + return status; +} + +struct smb2cli_validate_negotiate_info_state { + struct smbXcli_conn *conn; + DATA_BLOB in_input_buffer; + DATA_BLOB in_output_buffer; + DATA_BLOB out_input_buffer; + DATA_BLOB out_output_buffer; + uint16_t dialect; +}; + +static void smb2cli_validate_negotiate_info_done(struct tevent_req *subreq); + +struct tevent_req *smb2cli_validate_negotiate_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon) +{ + struct tevent_req *req; + struct smb2cli_validate_negotiate_info_state *state; + uint8_t *buf; + uint16_t dialect_count = 0; + struct tevent_req *subreq; + bool _save_should_sign; + size_t i; + + req = tevent_req_create(mem_ctx, &state, + struct smb2cli_validate_negotiate_info_state); + if (req == NULL) { + return NULL; + } + state->conn = conn; + + state->in_input_buffer = data_blob_talloc_zero(state, + 4 + 16 + 1 + 1 + 2); + if (tevent_req_nomem(state->in_input_buffer.data, req)) { + return tevent_req_post(req, ev); + } + buf = state->in_input_buffer.data; + + if (state->conn->max_protocol >= PROTOCOL_SMB3_00) { + SIVAL(buf, 0, conn->smb2.client.capabilities); + } else { + SIVAL(buf, 0, 0); /* Capabilities */ + } + if (state->conn->max_protocol >= PROTOCOL_SMB2_10) { + NTSTATUS status; + struct GUID_ndr_buf guid_buf = { .buf = {0}, }; + + status = GUID_to_ndr_buf(&conn->smb2.client.guid, + &guid_buf); + if (!NT_STATUS_IS_OK(status)) { + return NULL; + } + memcpy(buf+4, guid_buf.buf, 16); /* ClientGuid */ + } else { + memset(buf+4, 0, 16); /* ClientGuid */ + } + if (state->conn->min_protocol >= PROTOCOL_SMB2_02) { + SCVAL(buf, 20, conn->smb2.client.security_mode); + } else { + SCVAL(buf, 20, 0); + } + SCVAL(buf, 21, 0); /* reserved */ + + for (i=0; i < ARRAY_SIZE(smb2cli_prots); i++) { + bool ok; + size_t ofs; + + if (smb2cli_prots[i].proto < state->conn->min_protocol) { + continue; + } + + if (smb2cli_prots[i].proto > state->conn->max_protocol) { + continue; + } + + if (smb2cli_prots[i].proto == state->conn->protocol) { + state->dialect = smb2cli_prots[i].smb2_dialect; + } + + ofs = state->in_input_buffer.length; + ok = data_blob_realloc(state, &state->in_input_buffer, + ofs + 2); + if (!ok) { + tevent_req_oom(req); + return tevent_req_post(req, ev); + } + + buf = state->in_input_buffer.data; + SSVAL(buf, ofs, smb2cli_prots[i].smb2_dialect); + + dialect_count++; + } + buf = state->in_input_buffer.data; + SSVAL(buf, 22, dialect_count); + + _save_should_sign = smb2cli_tcon_is_signing_on(tcon); + smb2cli_tcon_should_sign(tcon, true); + subreq = smb2cli_ioctl_send(state, ev, conn, + timeout_msec, session, tcon, + UINT64_MAX, /* in_fid_persistent */ + UINT64_MAX, /* in_fid_volatile */ + FSCTL_VALIDATE_NEGOTIATE_INFO, + 0, /* in_max_input_length */ + &state->in_input_buffer, + 24, /* in_max_output_length */ + &state->in_output_buffer, + SMB2_IOCTL_FLAG_IS_FSCTL); + smb2cli_tcon_should_sign(tcon, _save_should_sign); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, + smb2cli_validate_negotiate_info_done, + req); + + return req; +} + +static void smb2cli_validate_negotiate_info_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct smb2cli_validate_negotiate_info_state *state = + tevent_req_data(req, + struct smb2cli_validate_negotiate_info_state); + NTSTATUS status; + const uint8_t *buf; + uint32_t capabilities; + DATA_BLOB guid_blob; + struct GUID server_guid; + uint16_t security_mode; + uint16_t dialect; + + status = smb2cli_ioctl_recv(subreq, state, + &state->out_input_buffer, + &state->out_output_buffer); + TALLOC_FREE(subreq); + + /* + * This response must be signed correctly for + * these "normal" error codes to be processed. + * If the packet wasn't signed correctly we will get + * NT_STATUS_ACCESS_DENIED or NT_STATUS_HMAC_NOT_SUPPORTED, + * or NT_STATUS_INVALID_NETWORK_RESPONSE + * from smb2_signing_check_pdu(). + * + * We must never ignore the above errors here. + */ + + if (NT_STATUS_EQUAL(status, NT_STATUS_FILE_CLOSED)) { + /* + * The response was signed, but not supported + * + * Older Windows and Samba releases return + * NT_STATUS_FILE_CLOSED. + */ + tevent_req_done(req); + return; + } + if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_DEVICE_REQUEST)) { + /* + * The response was signed, but not supported + * + * This is returned by the NTVFS based Samba 4.x file server + * for file shares. + */ + tevent_req_done(req); + return; + } + if (NT_STATUS_EQUAL(status, NT_STATUS_FS_DRIVER_REQUIRED)) { + /* + * The response was signed, but not supported + * + * This is returned by the NTVFS based Samba 4.x file server + * for ipc shares. + */ + tevent_req_done(req); + return; + } + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) { + /* + * The response was signed, but not supported + * + * This might be returned by older Windows versions or by + * NetApp SMB server implementations. + * + * See + * + * https://blogs.msdn.microsoft.com/openspecification/2012/06/28/smb3-secure-dialect-negotiation/ + * + */ + tevent_req_done(req); + return; + } + if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) { + /* + * The response was signed, but not supported + * + * This might be returned by NetApp Ontap 7.3.7 SMB server + * implementations. + * + * BUG: https://bugzilla.samba.org/show_bug.cgi?id=14607 + * + */ + tevent_req_done(req); + return; + } + if (tevent_req_nterror(req, status)) { + return; + } + + if (state->out_output_buffer.length != 24) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + buf = state->out_output_buffer.data; + + capabilities = IVAL(buf, 0); + guid_blob = data_blob_const(buf + 4, 16); + status = GUID_from_data_blob(&guid_blob, &server_guid); + if (tevent_req_nterror(req, status)) { + return; + } + security_mode = CVAL(buf, 20); + dialect = SVAL(buf, 22); + + if (capabilities != state->conn->smb2.server.capabilities) { + tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED); + return; + } + + if (!GUID_equal(&server_guid, &state->conn->smb2.server.guid)) { + tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED); + return; + } + + if (security_mode != state->conn->smb2.server.security_mode) { + tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED); + return; + } + + if (dialect != state->dialect) { + tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED); + return; + } + + tevent_req_done(req); +} + +NTSTATUS smb2cli_validate_negotiate_info_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +static int smbXcli_session_destructor(struct smbXcli_session *session) +{ + if (session->conn == NULL) { + return 0; + } + + DLIST_REMOVE(session->conn->sessions, session); + return 0; +} + +struct smbXcli_session *smbXcli_session_create(TALLOC_CTX *mem_ctx, + struct smbXcli_conn *conn) +{ + struct smbXcli_session *session; + NTSTATUS status; + + session = talloc_zero(mem_ctx, struct smbXcli_session); + if (session == NULL) { + return NULL; + } + session->smb2 = talloc_zero(session, struct smb2cli_session); + if (session->smb2 == NULL) { + talloc_free(session); + return NULL; + } + talloc_set_destructor(session, smbXcli_session_destructor); + + status = smb2_signing_key_sign_create(session->smb2, + conn->smb2.server.sign_algo, + NULL, /* no master key */ + NULL, /* derivations */ + &session->smb2->signing_key); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(session); + return NULL; + } + + DLIST_ADD_END(conn->sessions, session); + session->conn = conn; + + status = smb2_signing_key_sign_create(session, + conn->smb2.server.sign_algo, + NULL, /* no master key */ + NULL, /* derivations */ + &session->smb2_channel.signing_key); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(session); + return NULL; + } + + memcpy(session->smb2_channel.preauth_sha512, + conn->smb2.preauth_sha512, + sizeof(session->smb2_channel.preauth_sha512)); + + return session; +} + +struct smbXcli_session *smbXcli_session_shallow_copy(TALLOC_CTX *mem_ctx, + struct smbXcli_session *src) +{ + struct smbXcli_session *session; + struct timespec ts; + NTTIME nt; + + session = talloc_zero(mem_ctx, struct smbXcli_session); + if (session == NULL) { + return NULL; + } + session->smb2 = talloc_zero(session, struct smb2cli_session); + if (session->smb2 == NULL) { + talloc_free(session); + return NULL; + } + + /* + * Note we keep a pointer to the session keys of the + * main session and rely on the caller to free the + * shallow copy first! + */ + session->conn = src->conn; + *session->smb2 = *src->smb2; + session->smb2_channel = src->smb2_channel; + session->disconnect_expired = src->disconnect_expired; + + /* + * This is only supposed to be called in test code + * but we should not reuse nonces! + * + * Add the current timestamp as NTTIME to nonce_high + * and set nonce_low to a value we can recognize in captures. + */ + clock_gettime_mono(&ts); + nt = unix_timespec_to_nt_time(ts); + nt &= session->smb2->nonce_high_max; + if (nt == session->smb2->nonce_high_max || nt < UINT8_MAX) { + talloc_free(session); + return NULL; + } + session->smb2->nonce_high += nt; + session->smb2->nonce_low = UINT32_MAX; + + DLIST_ADD_END(src->conn->sessions, session); + talloc_set_destructor(session, smbXcli_session_destructor); + + return session; +} + +bool smbXcli_session_is_guest(struct smbXcli_session *session) +{ + if (session == NULL) { + return false; + } + + if (session->conn == NULL) { + return false; + } + + if (session->conn->mandatory_signing) { + return false; + } + + if (session->conn->protocol >= PROTOCOL_SMB2_02) { + if (session->smb2->session_flags & SMB2_SESSION_FLAG_IS_GUEST) { + return true; + } + return false; + } + + if (session->smb1.action & SMB_SETUP_GUEST) { + return true; + } + + return false; +} + +bool smbXcli_session_is_authenticated(struct smbXcli_session *session) +{ + const DATA_BLOB *application_key; + + if (session == NULL) { + return false; + } + + if (session->conn == NULL) { + return false; + } + + /* + * If we have an application key we had a session key negotiated + * at auth time. + */ + if (session->conn->protocol >= PROTOCOL_SMB2_02) { + if (!smb2_signing_key_valid(session->smb2->application_key)) { + return false; + } + application_key = &session->smb2->application_key->blob; + } else { + application_key = &session->smb1.application_key; + } + + if (application_key->length == 0) { + return false; + } + + return true; +} + +NTSTATUS smb2cli_session_signing_key(struct smbXcli_session *session, + TALLOC_CTX *mem_ctx, + DATA_BLOB *key) +{ + const struct smb2_signing_key *sig = NULL; + + if (session->conn == NULL) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + + /* + * Use channel signing key if there is one, otherwise fallback + * to session. + */ + + if (smb2_signing_key_valid(session->smb2_channel.signing_key)) { + sig = session->smb2_channel.signing_key; + } else if (smb2_signing_key_valid(session->smb2->signing_key)) { + sig = session->smb2->signing_key; + } else { + return NT_STATUS_NO_USER_SESSION_KEY; + } + + *key = data_blob_dup_talloc(mem_ctx, sig->blob); + if (key->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +NTSTATUS smb2cli_session_encryption_key(struct smbXcli_session *session, + TALLOC_CTX *mem_ctx, + DATA_BLOB *key) +{ + if (session->conn == NULL) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + + if (session->conn->protocol < PROTOCOL_SMB3_00) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + + if (!smb2_signing_key_valid(session->smb2->encryption_key)) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + + *key = data_blob_dup_talloc(mem_ctx, session->smb2->encryption_key->blob); + if (key->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +NTSTATUS smb2cli_session_decryption_key(struct smbXcli_session *session, + TALLOC_CTX *mem_ctx, + DATA_BLOB *key) +{ + if (session->conn == NULL) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + + if (session->conn->protocol < PROTOCOL_SMB3_00) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + + if (!smb2_signing_key_valid(session->smb2->decryption_key)) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + + *key = data_blob_dup_talloc(mem_ctx, session->smb2->decryption_key->blob); + if (key->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +NTSTATUS smbXcli_session_application_key(struct smbXcli_session *session, + TALLOC_CTX *mem_ctx, + DATA_BLOB *key) +{ + const DATA_BLOB *application_key; + + *key = data_blob_null; + + if (session->conn == NULL) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + + if (session->conn->protocol >= PROTOCOL_SMB2_02) { + if (!smb2_signing_key_valid(session->smb2->application_key)) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + application_key = &session->smb2->application_key->blob; + } else { + application_key = &session->smb1.application_key; + } + + if (application_key->length == 0) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + + *key = data_blob_dup_talloc(mem_ctx, *application_key); + if (key->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +void smbXcli_session_set_disconnect_expired(struct smbXcli_session *session) +{ + session->disconnect_expired = true; +} + +uint16_t smb1cli_session_current_id(struct smbXcli_session *session) +{ + return session->smb1.session_id; +} + +void smb1cli_session_set_id(struct smbXcli_session *session, + uint16_t session_id) +{ + session->smb1.session_id = session_id; +} + +void smb1cli_session_set_action(struct smbXcli_session *session, + uint16_t action) +{ + session->smb1.action = action; +} + +NTSTATUS smb1cli_session_set_session_key(struct smbXcli_session *session, + const DATA_BLOB _session_key) +{ + struct smbXcli_conn *conn = session->conn; + uint8_t session_key[16]; + + if (conn == NULL) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + if (session->smb1.application_key.length != 0) { + /* + * TODO: do not allow this... + * + * return NT_STATUS_INVALID_PARAMETER_MIX; + */ + data_blob_clear_free(&session->smb1.application_key); + session->smb1.protected_key = false; + } + + if (_session_key.length == 0) { + return NT_STATUS_OK; + } + + ZERO_STRUCT(session_key); + memcpy(session_key, _session_key.data, + MIN(_session_key.length, sizeof(session_key))); + + session->smb1.application_key = data_blob_talloc(session, + session_key, + sizeof(session_key)); + ZERO_STRUCT(session_key); + if (session->smb1.application_key.data == NULL) { + return NT_STATUS_NO_MEMORY; + } + + session->smb1.protected_key = false; + + return NT_STATUS_OK; +} + +NTSTATUS smb1cli_session_protect_session_key(struct smbXcli_session *session) +{ + NTSTATUS status; + + if (session->smb1.protected_key) { + /* already protected */ + return NT_STATUS_OK; + } + + if (session->smb1.application_key.length != 16) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + status = smb1_key_derivation(session->smb1.application_key.data, + session->smb1.application_key.length, + session->smb1.application_key.data); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + session->smb1.protected_key = true; + + return NT_STATUS_OK; +} + +uint8_t smb2cli_session_security_mode(struct smbXcli_session *session) +{ + struct smbXcli_conn *conn = session->conn; + uint8_t security_mode = 0; + + if (conn == NULL) { + return security_mode; + } + + security_mode = SMB2_NEGOTIATE_SIGNING_ENABLED; + if (conn->mandatory_signing) { + security_mode |= SMB2_NEGOTIATE_SIGNING_REQUIRED; + } + if (session->smb2->should_sign) { + security_mode |= SMB2_NEGOTIATE_SIGNING_REQUIRED; + } + + return security_mode; +} + +uint64_t smb2cli_session_current_id(struct smbXcli_session *session) +{ + return session->smb2->session_id; +} + +uint16_t smb2cli_session_get_flags(struct smbXcli_session *session) +{ + return session->smb2->session_flags; +} + +void smb2cli_session_set_id_and_flags(struct smbXcli_session *session, + uint64_t session_id, + uint16_t session_flags) +{ + session->smb2->session_id = session_id; + session->smb2->session_flags = session_flags; +} + +void smb2cli_session_increment_channel_sequence(struct smbXcli_session *session) +{ + session->smb2->channel_sequence += 1; +} + +uint16_t smb2cli_session_reset_channel_sequence(struct smbXcli_session *session, + uint16_t channel_sequence) +{ + uint16_t prev_cs; + + prev_cs = session->smb2->channel_sequence; + session->smb2->channel_sequence = channel_sequence; + + return prev_cs; +} + +uint16_t smb2cli_session_current_channel_sequence(struct smbXcli_session *session) +{ + return session->smb2->channel_sequence; +} + +void smb2cli_session_start_replay(struct smbXcli_session *session) +{ + session->smb2->replay_active = true; +} + +void smb2cli_session_stop_replay(struct smbXcli_session *session) +{ + session->smb2->replay_active = false; +} + +void smb2cli_session_require_signed_response(struct smbXcli_session *session, + bool require_signed_response) +{ + session->smb2->require_signed_response = require_signed_response; +} + +NTSTATUS smb2cli_session_update_preauth(struct smbXcli_session *session, + const struct iovec *iov) +{ + gnutls_hash_hd_t hash_hnd = NULL; + size_t i; + int rc; + + if (session->conn == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + if (session->conn->protocol < PROTOCOL_SMB3_11) { + return NT_STATUS_OK; + } + + if (smb2_signing_key_valid(session->smb2_channel.signing_key)) { + return NT_STATUS_OK; + } + + rc = gnutls_hash_init(&hash_hnd, + GNUTLS_DIG_SHA512); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED); + } + + rc = gnutls_hash(hash_hnd, + session->smb2_channel.preauth_sha512, + sizeof(session->smb2_channel.preauth_sha512)); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED); + } + for (i = 0; i < 3; i++) { + rc = gnutls_hash(hash_hnd, + iov[i].iov_base, + iov[i].iov_len); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED); + } + } + gnutls_hash_deinit(hash_hnd, session->smb2_channel.preauth_sha512); + + return NT_STATUS_OK; +} + +NTSTATUS smb2cli_session_set_session_key(struct smbXcli_session *session, + const DATA_BLOB _session_key, + const struct iovec *recv_iov) +{ + struct smbXcli_conn *conn = session->conn; + uint16_t no_sign_flags = 0; + bool check_signature = true; + uint32_t hdr_flags; + NTSTATUS status; + struct smb2_signing_derivations derivations = { + .signing = NULL, + }; + DATA_BLOB preauth_hash = data_blob_null; + size_t nonce_size = 0; + + if (conn == NULL) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + if (recv_iov[0].iov_len != SMB2_HDR_BODY) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + if (!conn->mandatory_signing) { + /* + * only allow guest sessions without + * mandatory signing. + * + * If we try an authentication with username != "" + * and the server let us in without verifying the + * password we don't have a negotiated session key + * for signing. + */ + no_sign_flags = SMB2_SESSION_FLAG_IS_GUEST; + } + + if (session->smb2->session_flags & no_sign_flags) { + session->smb2->should_sign = false; + return NT_STATUS_OK; + } + + if (smb2_signing_key_valid(session->smb2->signing_key)) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + if (conn->protocol >= PROTOCOL_SMB3_11) { + preauth_hash = data_blob_const(session->smb2_channel.preauth_sha512, + sizeof(session->smb2_channel.preauth_sha512)); + } + + smb2_signing_derivations_fill_const_stack(&derivations, + conn->protocol, + preauth_hash); + + status = smb2_signing_key_sign_create(session->smb2, + conn->smb2.server.sign_algo, + &_session_key, + derivations.signing, + &session->smb2->signing_key); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = smb2_signing_key_cipher_create(session->smb2, + conn->smb2.server.cipher, + &_session_key, + derivations.cipher_c2s, + &session->smb2->encryption_key); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = smb2_signing_key_cipher_create(session->smb2, + conn->smb2.server.cipher, + &_session_key, + derivations.cipher_s2c, + &session->smb2->decryption_key); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = smb2_signing_key_sign_create(session->smb2, + conn->smb2.server.sign_algo, + &_session_key, + derivations.application, + &session->smb2->application_key); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = smb2_signing_key_copy(session, + session->smb2->signing_key, + &session->smb2_channel.signing_key); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + check_signature = conn->mandatory_signing; + + hdr_flags = IVAL(recv_iov[0].iov_base, SMB2_HDR_FLAGS); + if (hdr_flags & SMB2_HDR_FLAG_SIGNED) { + /* + * Sadly some vendors don't sign the + * final SMB2 session setup response + * + * At least Windows and Samba are always doing this + * if there's a session key available. + * + * We only check the signature if it's mandatory + * or SMB2_HDR_FLAG_SIGNED is provided. + */ + check_signature = true; + } + + if (conn->protocol >= PROTOCOL_SMB3_11) { + check_signature = true; + } + + if (check_signature) { + status = smb2_signing_check_pdu(session->smb2_channel.signing_key, + recv_iov, 3); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + session->smb2->should_sign = false; + session->smb2->should_encrypt = false; + + if (conn->desire_signing) { + session->smb2->should_sign = true; + } + + if (conn->smb2.server.security_mode & SMB2_NEGOTIATE_SIGNING_REQUIRED) { + session->smb2->should_sign = true; + } + + if (session->smb2->session_flags & SMB2_SESSION_FLAG_ENCRYPT_DATA) { + session->smb2->should_encrypt = true; + } + + if (conn->protocol < PROTOCOL_SMB3_00) { + session->smb2->should_encrypt = false; + } + + if (conn->smb2.server.cipher == 0) { + session->smb2->should_encrypt = false; + } + + /* + * CCM and GCM algorithms must never have their + * nonce wrap, or the security of the whole + * communication and the keys is destroyed. + * We must drop the connection once we have + * transfered too much data. + * + * NOTE: We assume nonces greater than 8 bytes. + */ + generate_nonce_buffer((uint8_t *)&session->smb2->nonce_high_random, + sizeof(session->smb2->nonce_high_random)); + switch (conn->smb2.server.cipher) { + case SMB2_ENCRYPTION_AES128_CCM: + nonce_size = SMB2_AES_128_CCM_NONCE_SIZE; + break; + case SMB2_ENCRYPTION_AES128_GCM: + nonce_size = gnutls_cipher_get_iv_size(GNUTLS_CIPHER_AES_128_GCM); + break; + case SMB2_ENCRYPTION_AES256_CCM: + nonce_size = SMB2_AES_128_CCM_NONCE_SIZE; + break; + case SMB2_ENCRYPTION_AES256_GCM: + nonce_size = gnutls_cipher_get_iv_size(GNUTLS_CIPHER_AES_256_GCM); + break; + default: + nonce_size = 0; + break; + } + session->smb2->nonce_high_max = SMB2_NONCE_HIGH_MAX(nonce_size); + session->smb2->nonce_high = 0; + session->smb2->nonce_low = 0; + + return NT_STATUS_OK; +} + +NTSTATUS smb2cli_session_create_channel(TALLOC_CTX *mem_ctx, + struct smbXcli_session *session1, + struct smbXcli_conn *conn, + struct smbXcli_session **_session2) +{ + struct smbXcli_session *session2; + NTSTATUS status; + + if (!smb2_signing_key_valid(session1->smb2->signing_key)) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + if (conn == NULL) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + session2 = talloc_zero(mem_ctx, struct smbXcli_session); + if (session2 == NULL) { + return NT_STATUS_NO_MEMORY; + } + session2->smb2 = talloc_reference(session2, session1->smb2); + if (session2->smb2 == NULL) { + talloc_free(session2); + return NT_STATUS_NO_MEMORY; + } + + talloc_set_destructor(session2, smbXcli_session_destructor); + DLIST_ADD_END(conn->sessions, session2); + session2->conn = conn; + + status = smb2_signing_key_sign_create(session2, + conn->smb2.server.sign_algo, + NULL, /* no master key */ + NULL, /* derivations */ + &session2->smb2_channel.signing_key); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(session2); + return NT_STATUS_NO_MEMORY; + } + + memcpy(session2->smb2_channel.preauth_sha512, + conn->smb2.preauth_sha512, + sizeof(session2->smb2_channel.preauth_sha512)); + + *_session2 = session2; + return NT_STATUS_OK; +} + +NTSTATUS smb2cli_session_set_channel_key(struct smbXcli_session *session, + const DATA_BLOB _channel_key, + const struct iovec *recv_iov) +{ + struct smbXcli_conn *conn = session->conn; + uint8_t channel_key[16]; + NTSTATUS status; + struct _derivation { + DATA_BLOB label; + DATA_BLOB context; + }; + struct { + struct _derivation signing; + } derivation = { + .signing.label.length = 0, + }; + + if (conn == NULL) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + if (smb2_signing_key_valid(session->smb2_channel.signing_key)) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + if (conn->protocol >= PROTOCOL_SMB3_11) { + struct _derivation *d; + DATA_BLOB p; + + p = data_blob_const(session->smb2_channel.preauth_sha512, + sizeof(session->smb2_channel.preauth_sha512)); + + d = &derivation.signing; + d->label = data_blob_string_const_null("SMBSigningKey"); + d->context = p; + } else if (conn->protocol >= PROTOCOL_SMB3_00) { + struct _derivation *d; + + d = &derivation.signing; + d->label = data_blob_string_const_null("SMB2AESCMAC"); + d->context = data_blob_string_const_null("SmbSign"); + } + + ZERO_STRUCT(channel_key); + memcpy(channel_key, _channel_key.data, + MIN(_channel_key.length, sizeof(channel_key))); + + session->smb2_channel.signing_key->blob = + data_blob_talloc(session->smb2_channel.signing_key, + channel_key, + sizeof(channel_key)); + if (!smb2_signing_key_valid(session->smb2_channel.signing_key)) { + ZERO_STRUCT(channel_key); + return NT_STATUS_NO_MEMORY; + } + + if (conn->protocol >= PROTOCOL_SMB3_00) { + struct _derivation *d = &derivation.signing; + + status = smb2_key_derivation(channel_key, sizeof(channel_key), + d->label.data, d->label.length, + d->context.data, d->context.length, + session->smb2_channel.signing_key->blob.data, + session->smb2_channel.signing_key->blob.length); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + ZERO_STRUCT(channel_key); + + status = smb2_signing_check_pdu(session->smb2_channel.signing_key, + recv_iov, 3); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +NTSTATUS smb2cli_session_encryption_on(struct smbXcli_session *session) +{ + if (!session->smb2->should_sign) { + /* + * We need required signing on the session + * in order to prevent man in the middle attacks. + */ + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + if (session->smb2->should_encrypt) { + return NT_STATUS_OK; + } + + if (session->conn->protocol < PROTOCOL_SMB3_00) { + return NT_STATUS_NOT_SUPPORTED; + } + + if (session->conn->smb2.server.cipher == 0) { + return NT_STATUS_NOT_SUPPORTED; + } + + if (!smb2_signing_key_valid(session->smb2->signing_key)) { + return NT_STATUS_NOT_SUPPORTED; + } + session->smb2->should_encrypt = true; + return NT_STATUS_OK; +} + +uint16_t smb2cli_session_get_encryption_cipher(struct smbXcli_session *session) +{ + if (session->conn->protocol < PROTOCOL_SMB3_00) { + return 0; + } + + if (!session->smb2->should_encrypt) { + return 0; + } + + return session->conn->smb2.server.cipher; +} + +struct smbXcli_tcon *smbXcli_tcon_create(TALLOC_CTX *mem_ctx) +{ + struct smbXcli_tcon *tcon; + + tcon = talloc_zero(mem_ctx, struct smbXcli_tcon); + if (tcon == NULL) { + return NULL; + } + + return tcon; +} + +/* + * Return a deep structure copy of a struct smbXcli_tcon * + */ + +struct smbXcli_tcon *smbXcli_tcon_copy(TALLOC_CTX *mem_ctx, + const struct smbXcli_tcon *tcon_in) +{ + struct smbXcli_tcon *tcon; + + tcon = talloc_memdup(mem_ctx, tcon_in, sizeof(struct smbXcli_tcon)); + if (tcon == NULL) { + return NULL; + } + + /* Deal with the SMB1 strings. */ + if (tcon_in->smb1.service != NULL) { + tcon->smb1.service = talloc_strdup(tcon, tcon_in->smb1.service); + if (tcon->smb1.service == NULL) { + TALLOC_FREE(tcon); + return NULL; + } + } + if (tcon->smb1.fs_type != NULL) { + tcon->smb1.fs_type = talloc_strdup(tcon, tcon_in->smb1.fs_type); + if (tcon->smb1.fs_type == NULL) { + TALLOC_FREE(tcon); + return NULL; + } + } + return tcon; +} + +void smbXcli_tcon_set_fs_attributes(struct smbXcli_tcon *tcon, + uint32_t fs_attributes) +{ + tcon->fs_attributes = fs_attributes; +} + +uint32_t smbXcli_tcon_get_fs_attributes(struct smbXcli_tcon *tcon) +{ + return tcon->fs_attributes; +} + +bool smbXcli_tcon_is_dfs_share(struct smbXcli_tcon *tcon) +{ + if (tcon == NULL) { + return false; + } + + if (tcon->is_smb1) { + if (tcon->smb1.optional_support & SMB_SHARE_IN_DFS) { + return true; + } + + return false; + } + + if (tcon->smb2.capabilities & SMB2_SHARE_CAP_DFS) { + return true; + } + + return false; +} + +uint16_t smb1cli_tcon_current_id(struct smbXcli_tcon *tcon) +{ + return tcon->smb1.tcon_id; +} + +void smb1cli_tcon_set_id(struct smbXcli_tcon *tcon, uint16_t tcon_id) +{ + tcon->is_smb1 = true; + tcon->smb1.tcon_id = tcon_id; +} + +bool smb1cli_tcon_set_values(struct smbXcli_tcon *tcon, + uint16_t tcon_id, + uint16_t optional_support, + uint32_t maximal_access, + uint32_t guest_maximal_access, + const char *service, + const char *fs_type) +{ + tcon->is_smb1 = true; + tcon->fs_attributes = 0; + tcon->smb1.tcon_id = tcon_id; + tcon->smb1.optional_support = optional_support; + tcon->smb1.maximal_access = maximal_access; + tcon->smb1.guest_maximal_access = guest_maximal_access; + + TALLOC_FREE(tcon->smb1.service); + tcon->smb1.service = talloc_strdup(tcon, service); + if (service != NULL && tcon->smb1.service == NULL) { + return false; + } + + TALLOC_FREE(tcon->smb1.fs_type); + tcon->smb1.fs_type = talloc_strdup(tcon, fs_type); + if (fs_type != NULL && tcon->smb1.fs_type == NULL) { + return false; + } + + return true; +} + +uint32_t smb2cli_tcon_current_id(struct smbXcli_tcon *tcon) +{ + return tcon->smb2.tcon_id; +} + +void smb2cli_tcon_set_id(struct smbXcli_tcon *tcon, uint32_t tcon_id) +{ + tcon->smb2.tcon_id = tcon_id; +} + +uint32_t smb2cli_tcon_capabilities(struct smbXcli_tcon *tcon) +{ + return tcon->smb2.capabilities; +} + +uint32_t smb2cli_tcon_flags(struct smbXcli_tcon *tcon) +{ + return tcon->smb2.flags; +} + +void smb2cli_tcon_set_values(struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + uint32_t tcon_id, + uint8_t type, + uint32_t flags, + uint32_t capabilities, + uint32_t maximal_access) +{ + tcon->is_smb1 = false; + tcon->fs_attributes = 0; + tcon->smb2.tcon_id = tcon_id; + tcon->smb2.type = type; + tcon->smb2.flags = flags; + tcon->smb2.capabilities = capabilities; + tcon->smb2.maximal_access = maximal_access; + + tcon->smb2.should_sign = false; + tcon->smb2.should_encrypt = false; + + if (session == NULL) { + return; + } + + tcon->smb2.should_sign = session->smb2->should_sign; + tcon->smb2.should_encrypt = session->smb2->should_encrypt; + + if (flags & SMB2_SHAREFLAG_ENCRYPT_DATA) { + tcon->smb2.should_encrypt = true; + } +} + +void smb2cli_tcon_should_sign(struct smbXcli_tcon *tcon, + bool should_sign) +{ + tcon->smb2.should_sign = should_sign; +} + +bool smb2cli_tcon_is_signing_on(struct smbXcli_tcon *tcon) +{ + if (tcon->smb2.should_encrypt) { + return true; + } + + return tcon->smb2.should_sign; +} + +void smb2cli_tcon_should_encrypt(struct smbXcli_tcon *tcon, + bool should_encrypt) +{ + tcon->smb2.should_encrypt = should_encrypt; +} + +bool smb2cli_tcon_is_encryption_on(struct smbXcli_tcon *tcon) +{ + return tcon->smb2.should_encrypt; +} + +void smb2cli_conn_set_mid(struct smbXcli_conn *conn, uint64_t mid) +{ + conn->smb2.mid = mid; +} + +uint64_t smb2cli_conn_get_mid(struct smbXcli_conn *conn) +{ + return conn->smb2.mid; +} + +NTSTATUS smb2cli_parse_dyn_buffer(uint32_t dyn_offset, + const DATA_BLOB dyn_buffer, + uint32_t min_offset, + uint32_t buffer_offset, + uint32_t buffer_length, + uint32_t max_length, + uint32_t *next_offset, + DATA_BLOB *buffer) +{ + uint32_t offset; + bool oob; + + *buffer = data_blob_null; + *next_offset = dyn_offset; + + if (buffer_offset == 0) { + /* + * If the offset is 0, we better ignore + * the buffer_length field. + */ + return NT_STATUS_OK; + } + + if (buffer_length == 0) { + /* + * If the length is 0, we better ignore + * the buffer_offset field. + */ + return NT_STATUS_OK; + } + + if ((buffer_offset % 8) != 0) { + /* + * The offset needs to be 8 byte aligned. + */ + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + /* + * We used to enforce buffer_offset to be + * an exact match of the expected minimum, + * but the NetApp Ontap 7.3.7 SMB server + * gets the padding wrong and aligns the + * input_buffer_offset by a value of 8. + * + * So we just enforce that the offset is + * not lower than the expected value. + */ + SMB_ASSERT(min_offset >= dyn_offset); + if (buffer_offset < min_offset) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + /* + * Make [input|output]_buffer_offset relative to "dyn_buffer" + */ + offset = buffer_offset - dyn_offset; + oob = smb_buffer_oob(dyn_buffer.length, offset, buffer_length); + if (oob) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + /* + * Give the caller a hint what we consumed, + * the caller may need to add possible padding. + */ + *next_offset = buffer_offset + buffer_length; + + if (max_length == 0) { + /* + * If max_input_length is 0 we ignore the + * input_buffer_length, because Windows 2008 echos the + * DCERPC request from the requested input_buffer to + * the response input_buffer. + * + * We just use the same logic also for max_output_length... + */ + buffer_length = 0; + } + + if (buffer_length > max_length) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + *buffer = (DATA_BLOB) { + .data = dyn_buffer.data + offset, + .length = buffer_length, + }; + return NT_STATUS_OK; +} diff --git a/libcli/smb/smbXcli_base.h b/libcli/smb/smbXcli_base.h new file mode 100644 index 0000000..8e4fb81 --- /dev/null +++ b/libcli/smb/smbXcli_base.h @@ -0,0 +1,936 @@ +/* + Unix SMB/CIFS implementation. + Infrastructure for async SMB client requests + Copyright (C) Volker Lendecke 2008 + Copyright (C) Stefan Metzmacher 2011 + + 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 _SMBXCLI_BASE_H_ +#define _SMBXCLI_BASE_H_ + +#define SMB_SUICIDE_PACKET 0x74697865 + +struct smbXcli_conn; +struct smbXcli_session; +struct smbXcli_tcon; +struct smb_trans_enc_state; +struct GUID; +struct iovec; +struct smb2_create_blobs; +struct smb_create_returns; +struct smb311_capabilities; + +struct smbXcli_conn *smbXcli_conn_create(TALLOC_CTX *mem_ctx, + int fd, + const char *remote_name, + enum smb_signing_setting signing_state, + uint32_t smb1_capabilities, + struct GUID *client_guid, + uint32_t smb2_capabilities, + const struct smb311_capabilities *smb3_capabilities); + +bool smbXcli_conn_is_connected(struct smbXcli_conn *conn); +void smbXcli_conn_disconnect(struct smbXcli_conn *conn, NTSTATUS status); + +struct tevent_queue *smbXcli_conn_send_queue(struct smbXcli_conn *conn); +bool smbXcli_conn_has_async_calls(struct smbXcli_conn *conn); + +bool smbXcli_conn_dfs_supported(struct smbXcli_conn *conn); + +enum protocol_types smbXcli_conn_protocol(struct smbXcli_conn *conn); +bool smbXcli_conn_use_unicode(struct smbXcli_conn *conn); +bool smbXcli_conn_signing_mandatory(struct smbXcli_conn *conn); +bool smbXcli_conn_support_passthrough(struct smbXcli_conn *conn); + +void smbXcli_conn_set_sockopt(struct smbXcli_conn *conn, const char *options); +const struct sockaddr_storage *smbXcli_conn_local_sockaddr(struct smbXcli_conn *conn); +const struct sockaddr_storage *smbXcli_conn_remote_sockaddr(struct smbXcli_conn *conn); +const char *smbXcli_conn_remote_name(struct smbXcli_conn *conn); + +uint16_t smbXcli_conn_max_requests(struct smbXcli_conn *conn); +NTTIME smbXcli_conn_server_system_time(struct smbXcli_conn *conn); +const DATA_BLOB *smbXcli_conn_server_gss_blob(struct smbXcli_conn *conn); +const struct GUID *smbXcli_conn_server_guid(struct smbXcli_conn *conn); +bool smbXcli_conn_get_force_channel_sequence(struct smbXcli_conn *conn); +void smbXcli_conn_set_force_channel_sequence(struct smbXcli_conn *conn, + bool v); + + +struct tevent_req *smbXcli_conn_samba_suicide_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint8_t exitcode); +NTSTATUS smbXcli_conn_samba_suicide_recv(struct tevent_req *req); +NTSTATUS smbXcli_conn_samba_suicide(struct smbXcli_conn *conn, + uint8_t exitcode); + +void smbXcli_req_unset_pending(struct tevent_req *req); +bool smbXcli_req_set_pending(struct tevent_req *req); +struct timeval smbXcli_req_endtime(struct tevent_req *req); + +uint32_t smb1cli_conn_capabilities(struct smbXcli_conn *conn); +uint32_t smb1cli_conn_max_xmit(struct smbXcli_conn *conn); +bool smb1cli_conn_req_possible(struct smbXcli_conn *conn); +uint32_t smb1cli_conn_server_session_key(struct smbXcli_conn *conn); +const uint8_t *smb1cli_conn_server_challenge(struct smbXcli_conn *conn); +uint16_t smb1cli_conn_server_security_mode(struct smbXcli_conn *conn); +bool smb1cli_conn_server_readbraw(struct smbXcli_conn *conn); +bool smb1cli_conn_server_writebraw(struct smbXcli_conn *conn); +bool smb1cli_conn_server_lockread(struct smbXcli_conn *conn); +bool smb1cli_conn_server_writeunlock(struct smbXcli_conn *conn); +int smb1cli_conn_server_time_zone(struct smbXcli_conn *conn); + +bool smb1cli_conn_activate_signing(struct smbXcli_conn *conn, + const DATA_BLOB user_session_key, + const DATA_BLOB response); +bool smb1cli_conn_check_signing(struct smbXcli_conn *conn, + const uint8_t *buf, uint32_t seqnum); +bool smb1cli_conn_signing_is_active(struct smbXcli_conn *conn); + +void smb1cli_conn_set_encryption(struct smbXcli_conn *conn, + struct smb_trans_enc_state *es); +bool smb1cli_conn_encryption_on(struct smbXcli_conn *conn); + +bool smb1cli_is_andx_req(uint8_t cmd); +size_t smb1cli_req_wct_ofs(struct tevent_req **reqs, int num_reqs); + +uint16_t smb1cli_req_mid(struct tevent_req *req); +void smb1cli_req_set_mid(struct tevent_req *req, uint16_t mid); + +uint32_t smb1cli_req_seqnum(struct tevent_req *req); +void smb1cli_req_set_seqnum(struct tevent_req *req, uint32_t seqnum); + +struct smb1cli_req_expected_response { + NTSTATUS status; + uint8_t wct; +}; + +struct tevent_req *smb1cli_req_create(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint8_t smb_command, + uint8_t additional_flags, + uint8_t clear_flags, + uint16_t additional_flags2, + uint16_t clear_flags2, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + uint8_t wct, uint16_t *vwv, + int iov_count, + struct iovec *bytes_iov); +NTSTATUS smb1cli_req_chain_submit(struct tevent_req **reqs, int num_reqs); + +struct tevent_req *smb1cli_req_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint8_t smb_command, + uint8_t additional_flags, + uint8_t clear_flags, + uint16_t additional_flags2, + uint16_t clear_flags2, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + uint8_t wct, uint16_t *vwv, + uint32_t num_bytes, + const uint8_t *bytes); +NTSTATUS smb1cli_req_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct iovec **piov, + uint8_t **phdr, + uint8_t *pwct, + uint16_t **pvwv, + uint32_t *pvwv_offset, + uint32_t *pnum_bytes, + uint8_t **pbytes, + uint32_t *pbytes_offset, + uint8_t **pinbuf, + const struct smb1cli_req_expected_response *expected, + size_t num_expected); + +struct tevent_req *smb1cli_trans_send( + TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct smbXcli_conn *conn, uint8_t cmd, + uint8_t additional_flags, uint8_t clear_flags, + uint16_t additional_flags2, uint16_t clear_flags2, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + const char *pipe_name, uint16_t fid, uint16_t function, int flags, + uint16_t *setup, uint8_t num_setup, uint8_t max_setup, + uint8_t *param, uint32_t num_param, uint32_t max_param, + uint8_t *data, uint32_t num_data, uint32_t max_data); +NTSTATUS smb1cli_trans_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + uint16_t *recv_flags2, + uint16_t **setup, uint8_t min_setup, + uint8_t *num_setup, + uint8_t **param, uint32_t min_param, + uint32_t *num_param, + uint8_t **data, uint32_t min_data, + uint32_t *num_data); +NTSTATUS smb1cli_trans(TALLOC_CTX *mem_ctx, struct smbXcli_conn *conn, + uint8_t trans_cmd, + uint8_t additional_flags, uint8_t clear_flags, + uint16_t additional_flags2, uint16_t clear_flags2, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + const char *pipe_name, uint16_t fid, uint16_t function, + int flags, + uint16_t *setup, uint8_t num_setup, uint8_t max_setup, + uint8_t *param, uint32_t num_param, uint32_t max_param, + uint8_t *data, uint32_t num_data, uint32_t max_data, + uint16_t *recv_flags2, + uint16_t **rsetup, uint8_t min_rsetup, uint8_t *num_rsetup, + uint8_t **rparam, uint32_t min_rparam, uint32_t *num_rparam, + uint8_t **rdata, uint32_t min_rdata, uint32_t *num_rdata); + +struct tevent_req *smb1cli_echo_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + uint16_t num_echos, + DATA_BLOB data); +NTSTATUS smb1cli_echo_recv(struct tevent_req *req); +NTSTATUS smb1cli_echo(struct smbXcli_conn *conn, uint32_t timeout_msec, + uint16_t num_echos, DATA_BLOB data); + +struct tevent_req *smb1cli_session_setup_lm21_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_session *session, + uint16_t in_buf_size, + uint16_t in_mpx_max, + uint16_t in_vc_num, + uint32_t in_sess_key, + const char *in_user, + const char *in_domain, + const DATA_BLOB in_password, + const char *in_native_os, + const char *in_native_lm); +NTSTATUS smb1cli_session_setup_lm21_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + char **out_native_os, + char **out_native_lm); +struct tevent_req *smb1cli_session_setup_nt1_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_session *session, + uint16_t in_buf_size, + uint16_t in_mpx_max, + uint16_t in_vc_num, + uint32_t in_sess_key, + const char *in_user, + const char *in_domain, + const DATA_BLOB in_apassword, + const DATA_BLOB in_upassword, + uint32_t in_capabilities, + const char *in_native_os, + const char *in_native_lm); +NTSTATUS smb1cli_session_setup_nt1_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct iovec **precv_iov, + const uint8_t **precv_inbuf, + char **out_native_os, + char **out_native_lm, + char **out_primary_domain); +struct tevent_req *smb1cli_session_setup_ext_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_session *session, + uint16_t in_buf_size, + uint16_t in_mpx_max, + uint16_t in_vc_num, + uint32_t in_sess_key, + const DATA_BLOB in_security_blob, + uint32_t in_capabilities, + const char *in_native_os, + const char *in_native_lm); +NTSTATUS smb1cli_session_setup_ext_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct iovec **precv_iov, + const uint8_t **precv_inbuf, + DATA_BLOB *out_security_blob, + char **out_native_os, + char **out_native_lm); + +struct tevent_req *smb1cli_ntcreatex_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + const char *fname, + uint32_t CreatFlags, + uint32_t RootDirectoryFid, + uint32_t DesiredAccess, + uint64_t AllocationSize, + uint32_t FileAttributes, + uint32_t ShareAccess, + uint32_t CreateDisposition, + uint32_t CreateOptions, + uint32_t ImpersonationLevel, + uint8_t SecurityFlags); +NTSTATUS smb1cli_ntcreatex_recv(struct tevent_req *req, uint16_t *pfnum); +NTSTATUS smb1cli_ntcreatex(struct smbXcli_conn *conn, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + const char *fname, + uint32_t CreatFlags, + uint32_t RootDirectoryFid, + uint32_t DesiredAccess, + uint64_t AllocationSize, + uint32_t FileAttributes, + uint32_t ShareAccess, + uint32_t CreateDisposition, + uint32_t CreateOptions, + uint32_t ImpersonationLevel, + uint8_t SecurityFlags, + uint16_t *pfnum); +struct tevent_req *smb1cli_close_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + uint16_t fnum, + uint32_t last_modified); +NTSTATUS smb1cli_close_recv(struct tevent_req *req); +NTSTATUS smb1cli_close(struct smbXcli_conn *conn, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + uint16_t fnum, + uint32_t last_modified); +struct tevent_req *smb1cli_writex_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + uint16_t fnum, + uint16_t mode, + const uint8_t *buf, + uint64_t offset, + uint32_t size); +NTSTATUS smb1cli_writex_recv(struct tevent_req *req, + uint32_t *pwritten, + uint16_t *pavailable); +NTSTATUS smb1cli_writex(struct smbXcli_conn *conn, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + uint16_t fnum, + uint16_t mode, + const uint8_t *buf, + uint64_t offset, + uint32_t size, + uint32_t *pwritten, + uint16_t *pavailable); +struct tevent_req *smb1cli_readx_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + uint32_t pid, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + uint16_t fnum, + uint64_t offset, + uint32_t size); +NTSTATUS smb1cli_readx_recv(struct tevent_req *req, + uint32_t *received, + uint8_t **rcvbuf); + +bool smb2cli_conn_req_possible(struct smbXcli_conn *conn, uint32_t *max_dyn_len); +uint32_t smb2cli_conn_server_capabilities(struct smbXcli_conn *conn); +uint16_t smb2cli_conn_server_security_mode(struct smbXcli_conn *conn); +uint16_t smb2cli_conn_server_signing_algo(struct smbXcli_conn *conn); +uint16_t smb2cli_conn_server_encryption_algo(struct smbXcli_conn *conn); +uint32_t smb2cli_conn_max_trans_size(struct smbXcli_conn *conn); +uint32_t smb2cli_conn_max_read_size(struct smbXcli_conn *conn); +uint32_t smb2cli_conn_max_write_size(struct smbXcli_conn *conn); +void smb2cli_conn_set_max_credits(struct smbXcli_conn *conn, + uint16_t max_credits); +uint16_t smb2cli_conn_get_cur_credits(struct smbXcli_conn *conn); +uint8_t smb2cli_conn_get_io_priority(struct smbXcli_conn *conn); +void smb2cli_conn_set_io_priority(struct smbXcli_conn *conn, + uint8_t io_priority); +uint32_t smb2cli_conn_cc_chunk_len(struct smbXcli_conn *conn); +void smb2cli_conn_set_cc_chunk_len(struct smbXcli_conn *conn, + uint32_t chunk_len); +uint32_t smb2cli_conn_cc_max_chunks(struct smbXcli_conn *conn); +void smb2cli_conn_set_cc_max_chunks(struct smbXcli_conn *conn, + uint32_t max_chunks); +void smb2cli_conn_set_mid(struct smbXcli_conn *conn, uint64_t mid); +uint64_t smb2cli_conn_get_mid(struct smbXcli_conn *conn); + +NTSTATUS smb2cli_parse_dyn_buffer(uint32_t dyn_offset, + const DATA_BLOB dyn_buffer, + uint32_t min_offset, + uint32_t buffer_offset, + uint32_t buffer_length, + uint32_t max_length, + uint32_t *next_offset, + DATA_BLOB *buffer); + +struct tevent_req *smb2cli_req_create(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint16_t cmd, + uint32_t additional_flags, + uint32_t clear_flags, + uint32_t timeout_msec, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + const uint8_t *fixed, + uint16_t fixed_len, + const uint8_t *dyn, + uint32_t dyn_len, + uint32_t max_dyn_len); +void smb2cli_req_set_notify_async(struct tevent_req *req); +NTSTATUS smb2cli_req_compound_submit(struct tevent_req **reqs, + int num_reqs); +void smb2cli_req_set_credit_charge(struct tevent_req *req, uint16_t charge); + +struct smb2cli_req_expected_response { + NTSTATUS status; + uint16_t body_size; +}; + +struct tevent_req *smb2cli_req_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint16_t cmd, + uint32_t additional_flags, + uint32_t clear_flags, + uint32_t timeout_msec, + struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + const uint8_t *fixed, + uint16_t fixed_len, + const uint8_t *dyn, + uint32_t dyn_len, + uint32_t max_dyn_len); +NTSTATUS smb2cli_req_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct iovec **piov, + const struct smb2cli_req_expected_response *expected, + size_t num_expected); + +/* + * This expects an iov[3] array, that is filled with references to + * the buffers used for the sending the requests into the socket. + * + * This can only be called after smb2cli_req_recv(subreq) before + * the TALLOC_FREE(subreq). + */ +NTSTATUS smb2cli_req_get_sent_iov(struct tevent_req *req, + struct iovec *sent_iov); + +struct smb2_negotiate_contexts; +struct tevent_req *smbXcli_negprot_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + enum protocol_types min_protocol, + enum protocol_types max_protocol, + uint16_t max_credits, + struct smb2_negotiate_contexts *in_ctx); +NTSTATUS smbXcli_negprot_recv( + struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct smb2_negotiate_contexts **out_ctx); +NTSTATUS smbXcli_negprot(struct smbXcli_conn *conn, + uint32_t timeout_msec, + enum protocol_types min_protocol, + enum protocol_types max_protocol); + +struct tevent_req *smb2cli_validate_negotiate_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon); +NTSTATUS smb2cli_validate_negotiate_info_recv(struct tevent_req *req); + +struct smbXcli_session *smbXcli_session_create(TALLOC_CTX *mem_ctx, + struct smbXcli_conn *conn); +struct smbXcli_session *smbXcli_session_shallow_copy(TALLOC_CTX *mem_ctx, + struct smbXcli_session *src); +bool smbXcli_session_is_guest(struct smbXcli_session *session); +bool smbXcli_session_is_authenticated(struct smbXcli_session *session); +NTSTATUS smb2cli_session_signing_key(struct smbXcli_session *session, + TALLOC_CTX *mem_ctx, + DATA_BLOB *key); +NTSTATUS smb2cli_session_encryption_key(struct smbXcli_session *session, + TALLOC_CTX *mem_ctx, + DATA_BLOB *key); +NTSTATUS smb2cli_session_decryption_key(struct smbXcli_session *session, + TALLOC_CTX *mem_ctx, + DATA_BLOB *key); +NTSTATUS smbXcli_session_application_key(struct smbXcli_session *session, + TALLOC_CTX *mem_ctx, + DATA_BLOB *key); +void smbXcli_session_set_disconnect_expired(struct smbXcli_session *session); +uint16_t smb1cli_session_current_id(struct smbXcli_session* session); +void smb1cli_session_set_id(struct smbXcli_session* session, + uint16_t session_id); +void smb1cli_session_set_action(struct smbXcli_session *session, + uint16_t action); +NTSTATUS smb1cli_session_set_session_key(struct smbXcli_session *session, + const DATA_BLOB _session_key); +NTSTATUS smb1cli_session_protect_session_key(struct smbXcli_session *session); +uint8_t smb2cli_session_security_mode(struct smbXcli_session *session); +uint64_t smb2cli_session_current_id(struct smbXcli_session *session); +uint16_t smb2cli_session_get_flags(struct smbXcli_session *session); +void smb2cli_session_set_id_and_flags(struct smbXcli_session *session, + uint64_t session_id, + uint16_t session_flags); +void smb2cli_session_increment_channel_sequence(struct smbXcli_session *session); +uint16_t smb2cli_session_reset_channel_sequence(struct smbXcli_session *session, + uint16_t channel_sequence); +uint16_t smb2cli_session_current_channel_sequence(struct smbXcli_session *session); +void smb2cli_session_start_replay(struct smbXcli_session *session); +void smb2cli_session_stop_replay(struct smbXcli_session *session); +void smb2cli_session_require_signed_response(struct smbXcli_session *session, + bool require_signed_response); +NTSTATUS smb2cli_session_update_preauth(struct smbXcli_session *session, + const struct iovec *iov); +NTSTATUS smb2cli_session_set_session_key(struct smbXcli_session *session, + const DATA_BLOB session_key, + const struct iovec *recv_iov); +NTSTATUS smb2cli_session_create_channel(TALLOC_CTX *mem_ctx, + struct smbXcli_session *session1, + struct smbXcli_conn *conn, + struct smbXcli_session **_session2); +NTSTATUS smb2cli_session_set_channel_key(struct smbXcli_session *session, + const DATA_BLOB channel_key, + const struct iovec *recv_iov); +NTSTATUS smb2cli_session_encryption_on(struct smbXcli_session *session); +uint16_t smb2cli_session_get_encryption_cipher(struct smbXcli_session *session); + +struct smbXcli_tcon *smbXcli_tcon_create(TALLOC_CTX *mem_ctx); +struct smbXcli_tcon *smbXcli_tcon_copy(TALLOC_CTX *mem_ctx, + const struct smbXcli_tcon *tcon_in); +void smbXcli_tcon_set_fs_attributes(struct smbXcli_tcon *tcon, + uint32_t fs_attributes); +uint32_t smbXcli_tcon_get_fs_attributes(struct smbXcli_tcon *tcon); +bool smbXcli_tcon_is_dfs_share(struct smbXcli_tcon *tcon); +uint16_t smb1cli_tcon_current_id(struct smbXcli_tcon *tcon); +void smb1cli_tcon_set_id(struct smbXcli_tcon *tcon, uint16_t tcon_id); +bool smb1cli_tcon_set_values(struct smbXcli_tcon *tcon, + uint16_t tcon_id, + uint16_t optional_support, + uint32_t maximal_access, + uint32_t guest_maximal_access, + const char *service, + const char *fs_type); +uint32_t smb2cli_tcon_current_id(struct smbXcli_tcon *tcon); +void smb2cli_tcon_set_id(struct smbXcli_tcon *tcon, uint32_t tcon_id); +uint32_t smb2cli_tcon_capabilities(struct smbXcli_tcon *tcon); +uint32_t smb2cli_tcon_flags(struct smbXcli_tcon *tcon); +void smb2cli_tcon_set_values(struct smbXcli_tcon *tcon, + struct smbXcli_session *session, + uint32_t tcon_id, + uint8_t type, + uint32_t flags, + uint32_t capabilities, + uint32_t maximal_access); +void smb2cli_tcon_should_sign(struct smbXcli_tcon *tcon, + bool should_sign); +bool smb2cli_tcon_is_signing_on(struct smbXcli_tcon *tcon); +void smb2cli_tcon_should_encrypt(struct smbXcli_tcon *tcon, + bool should_encrypt); +bool smb2cli_tcon_is_encryption_on(struct smbXcli_tcon *tcon); + +struct tevent_req *smb2cli_session_setup_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + uint8_t in_flags, + uint32_t in_capabilities, + uint32_t in_channel, + uint64_t in_previous_session_id, + const DATA_BLOB *in_security_buffer); +NTSTATUS smb2cli_session_setup_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct iovec **recv_iov, + DATA_BLOB *out_security_buffer); + +struct tevent_req *smb2cli_logoff_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session); +NTSTATUS smb2cli_logoff_recv(struct tevent_req *req); +NTSTATUS smb2cli_logoff(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session); + +/* smb2cli_raw_tcon* should only be used in tests! */ +struct tevent_req *smb2cli_raw_tcon_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t additional_flags, + uint32_t clear_flags, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint16_t tcon_flags, + const char *unc); +NTSTATUS smb2cli_raw_tcon_recv(struct tevent_req *req); +NTSTATUS smb2cli_raw_tcon(struct smbXcli_conn *conn, + uint32_t additional_flags, + uint32_t clear_flags, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint16_t tcon_flags, + const char *unc); +struct tevent_req *smb2cli_tcon_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint16_t flags, + const char *unc); +NTSTATUS smb2cli_tcon_recv(struct tevent_req *req); +NTSTATUS smb2cli_tcon(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint16_t flags, + const char *unc); + +struct tevent_req *smb2cli_tdis_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon); +NTSTATUS smb2cli_tdis_recv(struct tevent_req *req); +NTSTATUS smb2cli_tdis(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon); + +struct tevent_req *smb2cli_create_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + const char *filename, + uint8_t oplock_level, /* SMB2_OPLOCK_LEVEL_* */ + uint32_t impersonation_level, /* SMB2_IMPERSONATION_* */ + uint32_t desired_access, + uint32_t file_attributes, + uint32_t share_access, + uint32_t create_disposition, + uint32_t create_options, + struct smb2_create_blobs *blobs); +NTSTATUS smb2cli_create_recv(struct tevent_req *req, + uint64_t *fid_persistent, + uint64_t *fid_volatile, + struct smb_create_returns *cr, + TALLOC_CTX *mem_ctx, + struct smb2_create_blobs *blobs); +NTSTATUS smb2cli_create(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + const char *filename, + uint8_t oplock_level, /* SMB2_OPLOCK_LEVEL_* */ + uint32_t impersonation_level, /* SMB2_IMPERSONATION_* */ + uint32_t desired_access, + uint32_t file_attributes, + uint32_t share_access, + uint32_t create_disposition, + uint32_t create_options, + struct smb2_create_blobs *blobs, + uint64_t *fid_persistent, + uint64_t *fid_volatile, + struct smb_create_returns *cr, + TALLOC_CTX *mem_ctx, + struct smb2_create_blobs *ret_blobs); + +struct tevent_req *smb2cli_close_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint16_t flags, + uint64_t fid_persistent, + uint64_t fid_volatile); +NTSTATUS smb2cli_close_recv(struct tevent_req *req); +NTSTATUS smb2cli_close(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint16_t flags, + uint64_t fid_persistent, + uint64_t fid_volatile); + +struct tevent_req *smb2cli_read_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint32_t length, + uint64_t offset, + uint64_t fid_persistent, + uint64_t fid_volatile, + uint64_t minimum_count, + uint64_t remaining_bytes); +NTSTATUS smb2cli_read_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + uint8_t **data, uint32_t *data_length); +NTSTATUS smb2cli_read(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint32_t length, + uint64_t offset, + uint64_t fid_persistent, + uint64_t fid_volatile, + uint64_t minimum_count, + uint64_t remaining_bytes, + TALLOC_CTX *mem_ctx, + uint8_t **data, + uint32_t *data_length); + +struct tevent_req *smb2cli_write_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint32_t length, + uint64_t offset, + uint64_t fid_persistent, + uint64_t fid_volatile, + uint32_t remaining_bytes, + uint32_t flags, + const uint8_t *data); +NTSTATUS smb2cli_write_recv(struct tevent_req *req, + uint32_t *written); +NTSTATUS smb2cli_write(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint32_t length, + uint64_t offset, + uint64_t fid_persistent, + uint64_t fid_volatile, + uint32_t remaining_bytes, + uint32_t flags, + const uint8_t *data, + uint32_t *written); + +struct tevent_req *smb2cli_flush_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint64_t fid_persistent, + uint64_t fid_volatile); +NTSTATUS smb2cli_flush_recv(struct tevent_req *req); +NTSTATUS smb2cli_flush(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint64_t fid_persistent, + uint64_t fid_volatile); + +struct tevent_req *smb2cli_set_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint8_t in_info_type, + uint8_t in_file_info_class, + const DATA_BLOB *in_input_buffer, + uint32_t in_additional_info, + uint64_t in_fid_persistent, + uint64_t in_fid_volatile); +NTSTATUS smb2cli_set_info_recv(struct tevent_req *req); +NTSTATUS smb2cli_set_info(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint8_t in_info_type, + uint8_t in_file_info_class, + const DATA_BLOB *in_input_buffer, + uint32_t in_additional_info, + uint64_t in_fid_persistent, + uint64_t in_fid_volatile); + +struct tevent_req *smb2cli_query_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint8_t in_info_type, + uint8_t in_file_info_class, + uint32_t in_max_output_length, + const DATA_BLOB *in_input_buffer, + uint32_t in_additional_info, + uint32_t in_flags, + uint64_t in_fid_persistent, + uint64_t in_fid_volatile); +NTSTATUS smb2cli_query_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + DATA_BLOB *out_output_buffer); +NTSTATUS smb2cli_query_info(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint8_t in_info_type, + uint8_t in_file_info_class, + uint32_t in_max_output_length, + const DATA_BLOB *in_input_buffer, + uint32_t in_additional_info, + uint32_t in_flags, + uint64_t in_fid_persistent, + uint64_t in_fid_volatile, + TALLOC_CTX *mem_ctx, + DATA_BLOB *out_output_buffer); + +struct tevent_req *smb2cli_query_directory_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint8_t level, + uint8_t flags, + uint32_t file_index, + uint64_t fid_persistent, + uint64_t fid_volatile, + const char *mask, + uint32_t outbuf_len); +NTSTATUS smb2cli_query_directory_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uint8_t **data, + uint32_t *data_length); +NTSTATUS smb2cli_query_directory(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint8_t level, + uint8_t flags, + uint32_t file_index, + uint64_t fid_persistent, + uint64_t fid_volatile, + const char *mask, + uint32_t outbuf_len, + TALLOC_CTX *mem_ctx, + uint8_t **data, + uint32_t *data_length); + +struct tevent_req *smb2cli_notify_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint32_t output_buffer_length, + uint64_t fid_persistent, + uint64_t fid_volatile, + uint32_t completion_filter, + bool recursive); +NTSTATUS smb2cli_notify_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + uint8_t **data, uint32_t *data_length); +NTSTATUS smb2cli_notify(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint32_t output_buffer_length, + uint64_t fid_persistent, + uint64_t fid_volatile, + uint32_t completion_filter, + bool recursive, + TALLOC_CTX *mem_ctx, + uint8_t **data, + uint32_t *data_length); + +struct tevent_req *smb2cli_ioctl_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint64_t in_fid_persistent, + uint64_t in_fid_volatile, + uint32_t in_ctl_code, + uint32_t in_max_input_length, + const DATA_BLOB *in_input_buffer, + uint32_t in_max_output_length, + const DATA_BLOB *in_output_buffer, + uint32_t in_flags); +NTSTATUS smb2cli_ioctl_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + DATA_BLOB *out_input_buffer, + DATA_BLOB *out_output_buffer); +NTSTATUS smb2cli_ioctl(struct smbXcli_conn *conn, + uint32_t timeout_msec, + struct smbXcli_session *session, + struct smbXcli_tcon *tcon, + uint64_t in_fid_persistent, + uint64_t in_fid_volatile, + uint32_t in_ctl_code, + uint32_t in_max_input_length, + const DATA_BLOB *in_input_buffer, + uint32_t in_max_output_length, + const DATA_BLOB *in_output_buffer, + uint32_t in_flags, + TALLOC_CTX *mem_ctx, + DATA_BLOB *out_input_buffer, + DATA_BLOB *out_output_buffer); + +struct tevent_req *smb2cli_echo_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbXcli_conn *conn, + uint32_t timeout_msec); +NTSTATUS smb2cli_echo_recv(struct tevent_req *req); +NTSTATUS smb2cli_echo(struct smbXcli_conn *conn, + uint32_t timeout_msec); + +#endif /* _SMBXCLI_BASE_H_ */ diff --git a/libcli/smb/smb_common.h b/libcli/smb/smb_common.h new file mode 100644 index 0000000..0117570 --- /dev/null +++ b/libcli/smb/smb_common.h @@ -0,0 +1,34 @@ +/* + Unix SMB/CIFS implementation. + + SMB and SMB2 common header + + Copyright (C) Stefan Metzmacher 2009 + + 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 __LIBCLI_SMB_SMB_COMMON_H__ +#define __LIBCLI_SMB_SMB_COMMON_H__ + +#include "libcli/smb/smb_constants.h" +#include "libcli/smb/smb2_constants.h" +#include "libcli/smb/smb2_create_blob.h" +#include "libcli/smb/smb2_lease.h" +#include "libcli/smb/smb2_lock.h" +#include "libcli/smb/smb2_signing.h" +#include "libcli/smb/smb_util.h" +#include "libcli/smb/smb_unix_ext.h" + +#endif diff --git a/libcli/smb/smb_constants.h b/libcli/smb/smb_constants.h new file mode 100644 index 0000000..a043cbc --- /dev/null +++ b/libcli/smb/smb_constants.h @@ -0,0 +1,621 @@ +/* + Unix SMB/CIFS implementation. + + SMB parameters and setup, plus a whole lot more. + + Copyright (C) Andrew Tridgell 2011 + + 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 _SMB_CONSTANTS_H +#define _SMB_CONSTANTS_H + +/* + * Netbios over TCP (rfc 1002) + */ +#define NBSSmessage 0x00 /* session message */ +#define NBSSrequest 0x81 /* session request */ +#define NBSSpositive 0x82 /* positiv session response */ +#define NBSSnegative 0x83 /* negativ session response */ +#define NBSSretarget 0x84 /* retarget session response */ +#define NBSSkeepalive 0x85 /* keepalive */ + +#define SMB_MAGIC 0x424D53FF /* 0xFF 'S' 'M' 'B' */ + +/* the basic packet size, assuming no words or bytes. Does not include the NBT header */ +#define MIN_SMB_SIZE 35 + +/* when using NBT encapsulation every packet has a 4 byte header */ +#define NBT_HDR_SIZE 4 + +/* offsets into message header for common items - NOTE: These have + changed from being offsets from the base of the NBT packet to the base of the SMB packet. + this has reduced all these values by 4 +*/ +#define HDR_COM 4 +#define HDR_RCLS 5 +#define HDR_REH 6 +#define HDR_ERR 7 +#define HDR_FLG 9 +#define HDR_FLG2 10 +#define HDR_PIDHIGH 12 +#define HDR_SS_FIELD 14 +#define HDR_TID 24 +#define HDR_PID 26 +#define HDR_UID 28 +#define HDR_MID 30 +#define HDR_WCT 32 +#define HDR_VWV 33 + +/* Macros for accessing SMB protocol elements */ +#define VWV(vwv) ((vwv)*2) + +#define smb_len_nbt(buf) (RIVAL(buf, 0) & 0x1FFFF) +#define _smb_setlen_nbt(buf,len) RSIVAL(buf, 0, (len) & 0x1FFFF) +#define smb_setlen_nbt(buf, len) do { \ + _smb_setlen_nbt(buf, len); \ + SIVAL(buf, 4, SMB_MAGIC); \ +} while (0) + +#define smb_len_tcp(buf) (RIVAL(buf, 0) & 0xFFFFFF) +#define _smb_setlen_tcp(buf,len) RSIVAL(buf, 0, (len) & 0xFFFFFF) +#define smb_setlen_tcp(buf, len) do { \ + _smb_setlen_tcp(buf, len); \ + SIVAL(buf, 4, SMB_MAGIC); \ +} while (0) + +/* protocol types. It assumes that higher protocols include lower protocols + as subsets. */ +enum protocol_types { + PROTOCOL_DEFAULT=-1, + PROTOCOL_NONE=0, + PROTOCOL_CORE, + PROTOCOL_COREPLUS, + PROTOCOL_LANMAN1, + PROTOCOL_LANMAN2, + PROTOCOL_NT1, + PROTOCOL_SMB2_02, + PROTOCOL_SMB2_10, + PROTOCOL_SMB3_00, + PROTOCOL_SMB3_02, + PROTOCOL_SMB3_11 +}; +#define PROTOCOL_LATEST PROTOCOL_SMB3_11 + +enum smb_signing_setting { + SMB_SIGNING_IPC_DEFAULT = -2, /* Only used in C code */ + SMB_SIGNING_DEFAULT = -1, + SMB_SIGNING_OFF = 0, + SMB_SIGNING_IF_REQUIRED = 1, + SMB_SIGNING_DESIRED = 2, + SMB_SIGNING_REQUIRED = 3, +}; + +/* This MUST align with 'enum smb_signing_setting' */ +enum smb_encryption_setting { + SMB_ENCRYPTION_DEFAULT = SMB_SIGNING_DEFAULT, + SMB_ENCRYPTION_OFF = SMB_SIGNING_OFF, + SMB_ENCRYPTION_IF_REQUIRED = SMB_SIGNING_IF_REQUIRED, + SMB_ENCRYPTION_DESIRED = SMB_SIGNING_DESIRED, + SMB_ENCRYPTION_REQUIRED = SMB_SIGNING_REQUIRED, +}; + +/* types of buffers in core SMB protocol */ +#define SMB_DATA_BLOCK 0x1 +#define SMB_ASCII4 0x4 + +/* flag defines. CIFS spec 3.1.1 */ +#define FLAG_SUPPORT_LOCKREAD 0x01 +#define FLAG_CLIENT_BUF_AVAIL 0x02 +#define FLAG_RESERVED 0x04 +#define FLAG_CASELESS_PATHNAMES 0x08 +#define FLAG_CANONICAL_PATHNAMES 0x10 +#define FLAG_REQUEST_OPLOCK 0x20 +#define FLAG_REQUEST_BATCH_OPLOCK 0x40 +#define FLAG_REPLY 0x80 + +/* the complete */ +#define SMBmkdir 0x00 /* create directory */ +#define SMBrmdir 0x01 /* delete directory */ +#define SMBopen 0x02 /* open file */ +#define SMBcreate 0x03 /* create file */ +#define SMBclose 0x04 /* close file */ +#define SMBflush 0x05 /* flush file */ +#define SMBunlink 0x06 /* delete file */ +#define SMBmv 0x07 /* rename file */ +#define SMBgetatr 0x08 /* get file attributes */ +#define SMBsetatr 0x09 /* set file attributes */ +#define SMBread 0x0A /* read from file */ +#define SMBwrite 0x0B /* write to file */ +#define SMBlock 0x0C /* lock byte range */ +#define SMBunlock 0x0D /* unlock byte range */ +#define SMBctemp 0x0E /* create temporary file */ +#define SMBmknew 0x0F /* make new file */ +#define SMBcheckpath 0x10 /* check directory path */ +#define SMBexit 0x11 /* process exit */ +#define SMBlseek 0x12 /* seek */ +#define SMBtcon 0x70 /* tree connect */ +#define SMBtconX 0x75 /* tree connect and X*/ +#define SMBtdis 0x71 /* tree disconnect */ +#define SMBnegprot 0x72 /* negotiate protocol */ +#define SMBdskattr 0x80 /* get disk attributes */ +#define SMBsearch 0x81 /* search directory */ +#define SMBsplopen 0xC0 /* open print spool file */ +#define SMBsplwr 0xC1 /* write to print spool file */ +#define SMBsplclose 0xC2 /* close print spool file */ +#define SMBsplretq 0xC3 /* return print queue */ +#define SMBsends 0xD0 /* send single block message */ +#define SMBsendb 0xD1 /* send broadcast message */ +#define SMBfwdname 0xD2 /* forward user name */ +#define SMBcancelf 0xD3 /* cancel forward */ +#define SMBgetmac 0xD4 /* get machine name */ +#define SMBsendstrt 0xD5 /* send start of multi-block message */ +#define SMBsendend 0xD6 /* send end of multi-block message */ +#define SMBsendtxt 0xD7 /* send text of multi-block message */ + +/* Core+ protocol */ +#define SMBlockread 0x13 /* Lock a range and read */ +#define SMBwriteunlock 0x14 /* Unlock a range then write */ +#define SMBreadbraw 0x1a /* read a block of data with no smb header */ +#define SMBwritebraw 0x1d /* write a block of data with no smb header */ +#define SMBwritec 0x20 /* secondary write request */ +#define SMBwriteclose 0x2c /* write a file then close it */ + +/* dos extended protocol */ +#define SMBreadBraw 0x1A /* read block raw */ +#define SMBreadBmpx 0x1B /* read block multiplexed */ +#define SMBreadBs 0x1C /* read block (secondary response) */ +#define SMBwriteBraw 0x1D /* write block raw */ +#define SMBwriteBmpx 0x1E /* write block multiplexed */ +#define SMBwriteBs 0x1F /* write block (secondary request) */ +#define SMBwriteC 0x20 /* write complete response */ +#define SMBsetattrE 0x22 /* set file attributes expanded */ +#define SMBgetattrE 0x23 /* get file attributes expanded */ +#define SMBlockingX 0x24 /* lock/unlock byte ranges and X */ +#define SMBtrans 0x25 /* transaction - name, bytes in/out */ +#define SMBtranss 0x26 /* transaction (secondary request/response) */ +#define SMBioctl 0x27 /* IOCTL */ +#define SMBioctls 0x28 /* IOCTL (secondary request/response) */ +#define SMBcopy 0x29 /* copy */ +#define SMBmove 0x2A /* move */ +#define SMBecho 0x2B /* echo */ +#define SMBopenX 0x2D /* open and X */ +#define SMBreadX 0x2E /* read and X */ +#define SMBwriteX 0x2F /* write and X */ +#define SMBsesssetupX 0x73 /* Session Set Up & X (including User Logon) */ +#define SMBffirst 0x82 /* find first */ +#define SMBfunique 0x83 /* find unique */ +#define SMBfclose 0x84 /* find close */ +#define SMBinvalid 0xFE /* invalid command */ + +/* Extended 2.0 protocol */ +#define SMBtrans2 0x32 /* TRANS2 protocol set */ +#define SMBtranss2 0x33 /* TRANS2 protocol set, secondary command */ +#define SMBfindclose 0x34 /* Terminate a TRANSACT2_FINDFIRST */ +#define SMBfindnclose 0x35 /* Terminate a TRANSACT2_FINDNOTIFYFIRST */ +#define SMBulogoffX 0x74 /* user logoff */ + +/* NT SMB extensions. */ +#define SMBnttrans 0xA0 /* NT transact */ +#define SMBnttranss 0xA1 /* NT transact secondary */ +#define SMBntcreateX 0xA2 /* NT create and X */ +#define SMBntcancel 0xA4 /* NT cancel */ +#define SMBntrename 0xA5 /* NT rename */ + +/* used to indicate end of chain */ +#define SMB_CHAIN_NONE 0xFF + +/* Sercurity mode bits. */ +#define NEGOTIATE_SECURITY_USER_LEVEL 0x01 +#define NEGOTIATE_SECURITY_CHALLENGE_RESPONSE 0x02 +#define NEGOTIATE_SECURITY_SIGNATURES_ENABLED 0x04 +#define NEGOTIATE_SECURITY_SIGNATURES_REQUIRED 0x08 + +/* + * The negotiated buffer size for non LARGE_READX/WRITEX + * should be limited to uint16_t and has to be at least + * 500, which is the default for MinClientBufferSize on Windows. + */ +#define SMB_BUFFER_SIZE_MIN 500 +#define SMB_BUFFER_SIZE_MAX 65535 + +/* Capabilities. see ftp.microsoft.com/developr/drg/cifs/cifs/cifs4.txt */ + +#define CAP_RAW_MODE 0x00000001 +#define CAP_MPX_MODE 0x00000002 +#define CAP_UNICODE 0x00000004 +#define CAP_LARGE_FILES 0x00000008 +#define CAP_NT_SMBS 0x00000010 +#define CAP_RPC_REMOTE_APIS 0x00000020 +#define CAP_STATUS32 0x00000040 +#define CAP_LEVEL_II_OPLOCKS 0x00000080 +#define CAP_LOCK_AND_READ 0x00000100 +#define CAP_NT_FIND 0x00000200 +#define CAP_DFS 0x00001000 +#define CAP_W2K_SMBS 0x00002000 +#define CAP_LARGE_READX 0x00004000 +#define CAP_LARGE_WRITEX 0x00008000 +#define CAP_LWIO 0x00010000 +#define CAP_UNIX 0x00800000 /* Capabilities for UNIX extensions. Created by HP. */ +#define CAP_DYNAMIC_REAUTH 0x20000000 +#define CAP_EXTENDED_SECURITY 0x80000000 + +#define SMB_CAP_BOTH_MASK ( \ + CAP_UNICODE | \ + CAP_NT_SMBS | \ + CAP_STATUS32 | \ + CAP_LEVEL_II_OPLOCKS | \ + CAP_EXTENDED_SECURITY | \ + 0) +#define SMB_CAP_SERVER_MASK ( \ + CAP_RAW_MODE | \ + CAP_MPX_MODE | \ + CAP_LARGE_FILES | \ + CAP_RPC_REMOTE_APIS | \ + CAP_LOCK_AND_READ | \ + CAP_NT_FIND | \ + CAP_DFS | \ + CAP_W2K_SMBS | \ + CAP_LARGE_READX | \ + CAP_LARGE_WRITEX | \ + CAP_LWIO | \ + CAP_UNIX | \ + 0) +#define SMB_CAP_CLIENT_MASK ( \ + CAP_DYNAMIC_REAUTH | \ + 0) +/* + * Older Samba releases (<= 3.6.x) + * expect the client to send CAP_LARGE_READX + * in order to let the client use large reads. + */ +#define SMB_CAP_LEGACY_CLIENT_MASK ( \ + SMB_CAP_CLIENT_MASK | \ + CAP_LARGE_READX | \ + CAP_LARGE_WRITEX | \ + 0) + +/* + * The action flags in the SMB session setup response + */ +#define SMB_SETUP_GUEST 0x0001 +#define SMB_SETUP_USE_LANMAN_KEY 0x0002 + +/* Client-side offline caching policy types */ +enum csc_policy { + CSC_POLICY_MANUAL=0, + CSC_POLICY_DOCUMENTS=1, + CSC_POLICY_PROGRAMS=2, + CSC_POLICY_DISABLE=3 +}; + +/* TCONX Flag (smb_vwv2). [MS-SMB] 2.2.4.7.1 */ +#define TCONX_FLAG_DISCONNECT_TID 0x0001 +#define TCONX_FLAG_EXTENDED_SIGNATURES 0x0004 +#define TCONX_FLAG_EXTENDED_RESPONSE 0x0008 + +/* this is used on a TConX. [MS-SMB] 2.2.4.7.2 */ +#define SMB_SUPPORT_SEARCH_BITS 0x0001 +#define SMB_SHARE_IN_DFS 0x0002 +#define SMB_CSC_MASK 0x000C +#define SMB_CSC_POLICY_SHIFT 2 +#define SMB_UNIQUE_FILE_NAME 0x0010 +#define SMB_EXTENDED_SIGNATURES 0x0020 + +/* NT Flags2 bits - cifs6.txt section 3.1.2 */ +#define FLAGS2_LONG_PATH_COMPONENTS 0x0001 +#define FLAGS2_EXTENDED_ATTRIBUTES 0x0002 +#define FLAGS2_SMB_SECURITY_SIGNATURES 0x0004 +#define FLAGS2_COMPRESSED 0x0008 /* MS-SMB */ +#define FLAGS2_SMB_SECURITY_SIGNATURES_REQUIRED 0x0010 +#define FLAGS2_IS_LONG_NAME 0x0040 +#define FLAGS2_REPARSE_PATH 0x0400 /* MS-SMB @GMT- path. */ +#define FLAGS2_EXTENDED_SECURITY 0x0800 +#define FLAGS2_DFS_PATHNAMES 0x1000 +#define FLAGS2_READ_PERMIT_EXECUTE 0x2000 +#define FLAGS2_32_BIT_ERROR_CODES 0x4000 +#define FLAGS2_UNICODE_STRINGS 0x8000 + +/* FileAttributes (search attributes) field */ +#define FILE_ATTRIBUTE_READONLY 0x0001L +#define FILE_ATTRIBUTE_HIDDEN 0x0002L +#define FILE_ATTRIBUTE_SYSTEM 0x0004L +#define FILE_ATTRIBUTE_VOLUME 0x0008L +#define FILE_ATTRIBUTE_DIRECTORY 0x0010L +#define FILE_ATTRIBUTE_ARCHIVE 0x0020L +#define FILE_ATTRIBUTE_DEVICE 0x0040L +#define FILE_ATTRIBUTE_NORMAL 0x0080L +#define FILE_ATTRIBUTE_TEMPORARY 0x0100L +#define FILE_ATTRIBUTE_SPARSE 0x0200L +#define FILE_ATTRIBUTE_REPARSE_POINT 0x0400L +#define FILE_ATTRIBUTE_COMPRESSED 0x0800L +#define FILE_ATTRIBUTE_OFFLINE 0x1000L +#define FILE_ATTRIBUTE_NONINDEXED 0x2000L +#define FILE_ATTRIBUTE_ENCRYPTED 0x4000L +#define FILE_ATTRIBUTE_ALL_MASK 0x7FFFL + +#define SAMBA_ATTRIBUTES_MASK (FILE_ATTRIBUTE_READONLY|\ + FILE_ATTRIBUTE_HIDDEN|\ + FILE_ATTRIBUTE_SYSTEM|\ + FILE_ATTRIBUTE_DIRECTORY|\ + FILE_ATTRIBUTE_ARCHIVE|\ + FILE_ATTRIBUTE_OFFLINE) + +/* File type flags */ +#define FILE_TYPE_DISK 0 +#define FILE_TYPE_BYTE_MODE_PIPE 1 +#define FILE_TYPE_MESSAGE_MODE_PIPE 2 +#define FILE_TYPE_PRINTER 3 +#define FILE_TYPE_COMM_DEVICE 4 +#define FILE_TYPE_UNKNOWN 0xFFFF + +/* Lock types. */ +#define LOCKING_ANDX_EXCLUSIVE_LOCK 0x00 +#define LOCKING_ANDX_SHARED_LOCK 0x01 +#define LOCKING_ANDX_OPLOCK_RELEASE 0x02 +#define LOCKING_ANDX_CHANGE_LOCKTYPE 0x04 +#define LOCKING_ANDX_CANCEL_LOCK 0x08 +#define LOCKING_ANDX_LARGE_FILES 0x10 + +/* + * Bits we test with. + */ + +#define OPLOCK_NONE 0 +#define OPLOCK_EXCLUSIVE 1 +#define OPLOCK_BATCH 2 +#define OPLOCK_LEVEL_II 4 + +#define CORE_OPLOCK_GRANTED (1<<5) +#define EXTENDED_OPLOCK_GRANTED (1<<15) + +/* + * Return values for oplock types. + */ + +#define NO_OPLOCK_RETURN 0 +#define EXCLUSIVE_OPLOCK_RETURN 1 +#define BATCH_OPLOCK_RETURN 2 +#define LEVEL_II_OPLOCK_RETURN 3 + +/* oplock levels sent in oplock break */ +#define OPLOCK_BREAK_TO_NONE 0 +#define OPLOCK_BREAK_TO_LEVEL_II 1 + +/* Filesystem Attributes. */ +#define FILE_CASE_SENSITIVE_SEARCH 0x00000001 +#define FILE_CASE_PRESERVED_NAMES 0x00000002 +#define FILE_UNICODE_ON_DISK 0x00000004 +/* According to cifs9f, this is 4, not 8 */ +/* Acconding to testing, this actually sets the security attribute! */ +#define FILE_PERSISTENT_ACLS 0x00000008 +#define FILE_FILE_COMPRESSION 0x00000010 +#define FILE_VOLUME_QUOTAS 0x00000020 +#define FILE_SUPPORTS_SPARSE_FILES 0x00000040 +#define FILE_SUPPORTS_REPARSE_POINTS 0x00000080 +#define FILE_SUPPORTS_REMOTE_STORAGE 0x00000100 +#define FS_LFN_APIS 0x00004000 +#define FILE_VOLUME_IS_COMPRESSED 0x00008000 +#define FILE_SUPPORTS_OBJECT_IDS 0x00010000 +#define FILE_SUPPORTS_ENCRYPTION 0x00020000 +#define FILE_NAMED_STREAMS 0x00040000 +#define FILE_READ_ONLY_VOLUME 0x00080000 +#define FILE_SUPPORTS_BLOCK_REFCOUNTING 0x08000000 + +/* ShareAccess field. */ +#define FILE_SHARE_NONE 0 /* Cannot be used in bitmask. */ +#define FILE_SHARE_READ 1 +#define FILE_SHARE_WRITE 2 +#define FILE_SHARE_DELETE 4 + +/* Flags - combined with attributes. */ +#define FILE_FLAG_WRITE_THROUGH 0x80000000L +#define FILE_FLAG_NO_BUFFERING 0x20000000L +#define FILE_FLAG_RANDOM_ACCESS 0x10000000L +#define FILE_FLAG_SEQUENTIAL_SCAN 0x08000000L +#define FILE_FLAG_DELETE_ON_CLOSE 0x04000000L +#define FILE_FLAG_BACKUP_SEMANTICS 0x02000000L +#define FILE_FLAG_POSIX_SEMANTICS 0x01000000L + +/* CreateDisposition field. */ +#define FILE_SUPERSEDE 0 /* File exists overwrite/supersede. File not exist create. */ +#define FILE_OPEN 1 /* File exists open. File not exist fail. */ +#define FILE_CREATE 2 /* File exists fail. File not exist create. */ +#define FILE_OPEN_IF 3 /* File exists open. File not exist create. */ +#define FILE_OVERWRITE 4 /* File exists overwrite. File not exist fail. */ +#define FILE_OVERWRITE_IF 5 /* File exists overwrite. File not exist create. */ + +/* CreateOptions field. */ +#define FILE_DIRECTORY_FILE 0x0001 +#define FILE_WRITE_THROUGH 0x0002 +#define FILE_SEQUENTIAL_ONLY 0x0004 +#define FILE_NO_INTERMEDIATE_BUFFERING 0x0008 +#define FILE_SYNCHRONOUS_IO_ALERT 0x0010 /* may be ignored */ +#define FILE_SYNCHRONOUS_IO_NONALERT 0x0020 /* may be ignored */ +#define FILE_NON_DIRECTORY_FILE 0x0040 +#define FILE_CREATE_TREE_CONNECTION 0x0080 /* ignore, should be zero */ +#define FILE_COMPLETE_IF_OPLOCKED 0x0100 /* ignore, should be zero */ +#define FILE_NO_EA_KNOWLEDGE 0x0200 +#define FILE_EIGHT_DOT_THREE_ONLY 0x0400 /* aka OPEN_FOR_RECOVERY: ignore, should be zero */ +#define FILE_RANDOM_ACCESS 0x0800 +#define FILE_DELETE_ON_CLOSE 0x1000 +#define FILE_OPEN_BY_FILE_ID 0x2000 +#define FILE_OPEN_FOR_BACKUP_INTENT 0x4000 +#define FILE_NO_COMPRESSION 0x8000 +#define FILE_RESERVER_OPFILTER 0x00100000 /* ignore, should be zero */ +#define FILE_OPEN_REPARSE_POINT 0x00200000 +#define FILE_OPEN_NO_RECALL 0x00400000 +#define FILE_OPEN_FOR_FREE_SPACE_QUERY 0x00800000 /* ignore should be zero */ + +/* Responses when opening a file. */ +#define FILE_WAS_SUPERSEDED 0 +#define FILE_WAS_OPENED 1 +#define FILE_WAS_CREATED 2 +#define FILE_WAS_OVERWRITTEN 3 + +/* These are the trans subcommands */ +#define TRANSACT_SETNAMEDPIPEHANDLESTATE 0x01 +#define TRANSACT_DCERPCCMD 0x26 +#define TRANSACT_WAITNAMEDPIPEHANDLESTATE 0x53 + +/* These are the TRANS2 sub commands */ +#define TRANSACT2_OPEN 0 +#define TRANSACT2_FINDFIRST 1 +#define TRANSACT2_FINDNEXT 2 +#define TRANSACT2_QFSINFO 3 +#define TRANSACT2_SETFSINFO 4 +#define TRANSACT2_QPATHINFO 5 +#define TRANSACT2_SETPATHINFO 6 +#define TRANSACT2_QFILEINFO 7 +#define TRANSACT2_SETFILEINFO 8 +#define TRANSACT2_FSCTL 9 +#define TRANSACT2_IOCTL 0xA +#define TRANSACT2_FINDNOTIFYFIRST 0xB +#define TRANSACT2_FINDNOTIFYNEXT 0xC +#define TRANSACT2_MKDIR 0xD +#define TRANSACT2_SESSION_SETUP 0xE +#define TRANSACT2_GET_DFS_REFERRAL 0x10 +#define TRANSACT2_REPORT_DFS_INCONSISTANCY 0x11 + +/* These are the NT transact sub commands. */ +#define NT_TRANSACT_CREATE 1 +#define NT_TRANSACT_IOCTL 2 +#define NT_TRANSACT_SET_SECURITY_DESC 3 +#define NT_TRANSACT_NOTIFY_CHANGE 4 +#define NT_TRANSACT_RENAME 5 +#define NT_TRANSACT_QUERY_SECURITY_DESC 6 +#define NT_TRANSACT_GET_USER_QUOTA 7 +#define NT_TRANSACT_SET_USER_QUOTA 8 + +/* ioctl codes */ +#define IOCTL_QUERY_JOB_INFO 0x530060 + +/* filesystem control codes */ +#define FSCTL_METHOD_BUFFERED 0x00000000 +#define FSCTL_METHOD_IN_DIRECT 0x00000001 +#define FSCTL_METHOD_OUT_DIRECT 0x00000002 +#define FSCTL_METHOD_NEITHER 0x00000003 + +#define FSCTL_ACCESS_ANY 0x00000000 +#define FSCTL_ACCESS_READ 0x00004000 +#define FSCTL_ACCESS_WRITE 0x00008000 + +#define IOCTL_DEV_TYPE_MASK 0xFFFF0000 + +#define FSCTL_DFS 0x00060000 +#define FSCTL_DFS_GET_REFERRALS (FSCTL_DFS | FSCTL_ACCESS_ANY | 0x0194 | FSCTL_METHOD_BUFFERED) +#define FSCTL_DFS_GET_REFERRALS_EX (FSCTL_DFS | FSCTL_ACCESS_ANY | 0x01B0 | FSCTL_METHOD_BUFFERED) + +#define FSCTL_FILESYSTEM 0x00090000 +#define FSCTL_REQUEST_OPLOCK_LEVEL_1 (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x0000 | FSCTL_METHOD_BUFFERED) +#define FSCTL_REQUEST_OPLOCK_LEVEL_2 (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x0004 | FSCTL_METHOD_BUFFERED) +#define FSCTL_REQUEST_BATCH_OPLOCK (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x0008 | FSCTL_METHOD_BUFFERED) +#define FSCTL_OPLOCK_BREAK_ACKNOWLEDGE (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x000C | FSCTL_METHOD_BUFFERED) +#define FSCTL_OPBATCH_ACK_CLOSE_PENDING (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x0010 | FSCTL_METHOD_BUFFERED) +#define FSCTL_OPLOCK_BREAK_NOTIFY (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x0014 | FSCTL_METHOD_BUFFERED) +#define FSCTL_GET_COMPRESSION (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x003C | FSCTL_METHOD_BUFFERED) +#define FSCTL_SET_COMPRESSION (FSCTL_FILESYSTEM | FSCTL_ACCESS_READ \ + | FSCTL_ACCESS_WRITE | 0x0040 | FSCTL_METHOD_BUFFERED) +#define FSCTL_FILESYS_GET_STATISTICS (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x0060 | FSCTL_METHOD_BUFFERED) +#define FSCTL_GET_NTFS_VOLUME_DATA (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x0064 | FSCTL_METHOD_BUFFERED) +#define FSCTL_IS_VOLUME_DIRTY (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x0078 | FSCTL_METHOD_BUFFERED) +#define FSCTL_FIND_FILES_BY_SID (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x008C | FSCTL_METHOD_NEITHER) +#define FSCTL_SET_OBJECT_ID (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x0098 | FSCTL_METHOD_BUFFERED) +#define FSCTL_GET_OBJECT_ID (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x009C | FSCTL_METHOD_BUFFERED) +#define FSCTL_DELETE_OBJECT_ID (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x00A0 | FSCTL_METHOD_BUFFERED) +#define FSCTL_SET_REPARSE_POINT (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x00A4 | FSCTL_METHOD_BUFFERED) +#define FSCTL_GET_REPARSE_POINT (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x00A8 | FSCTL_METHOD_BUFFERED) +#define FSCTL_DELETE_REPARSE_POINT (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x00AC | FSCTL_METHOD_BUFFERED) +#define FSCTL_SET_OBJECT_ID_EXTENDED (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x00BC | FSCTL_METHOD_BUFFERED) +#define FSCTL_CREATE_OR_GET_OBJECT_ID (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x00C0 | FSCTL_METHOD_BUFFERED) +#define FSCTL_SET_SPARSE (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x00C4 | FSCTL_METHOD_BUFFERED) +#define FSCTL_SET_ZERO_DATA (FSCTL_FILESYSTEM | FSCTL_ACCESS_WRITE | 0x00C8 | FSCTL_METHOD_BUFFERED) +#define FSCTL_SET_ZERO_ON_DEALLOCATION (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x0194 | FSCTL_METHOD_BUFFERED) +#define FSCTL_READ_FILE_USN_DATA (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x00EB | FSCTL_METHOD_BUFFERED) +#define FSCTL_WRITE_USN_CLOSE_RECORD (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x00EF | FSCTL_METHOD_BUFFERED) +#define FSCTL_QUERY_ALLOCATED_RANGES (FSCTL_FILESYSTEM | FSCTL_ACCESS_READ | 0x00CC | FSCTL_METHOD_NEITHER) +#define FSCTL_QUERY_ON_DISK_VOLUME_INFO (FSCTL_FILESYSTEM | FSCTL_ACCESS_WRITE | 0x013C | FSCTL_METHOD_BUFFERED) +#define FSCTL_QUERY_SPARING_INFO (FSCTL_FILESYSTEM | FSCTL_ACCESS_WRITE | 0x0138 | FSCTL_METHOD_BUFFERED) +#define FSCTL_FILE_LEVEL_TRIM (FSCTL_FILESYSTEM | FSCTL_ACCESS_WRITE | 0x0208 | FSCTL_METHOD_BUFFERED) +#define FSCTL_OFFLOAD_READ (FSCTL_FILESYSTEM | FSCTL_ACCESS_READ | 0x0264 | FSCTL_METHOD_BUFFERED) +#define FSCTL_OFFLOAD_WRITE (FSCTL_FILESYSTEM | FSCTL_ACCESS_WRITE | 0x0268 | FSCTL_METHOD_BUFFERED) +#define FSCTL_SET_INTEGRITY_INFORMATION (FSCTL_FILESYSTEM | FSCTL_ACCESS_READ \ + | FSCTL_ACCESS_WRITE | 0x0280 | FSCTL_METHOD_BUFFERED) +/* this one is really FSCTL_DUPLICATE_EXTENTS_TO_FILE in the MS docs: */ +#define FSCTL_DUP_EXTENTS_TO_FILE (FSCTL_FILESYSTEM | FSCTL_ACCESS_WRITE | 0x0344 | FSCTL_METHOD_BUFFERED) +#define FSCTL_DUPLICATE_EXTENTS_TO_FILE_EX (FSCTL_FILESYSTEM | FSCTL_ACCESS_WRITE | 0x03E8 | FSCTL_METHOD_BUFFERED) +#define FSCTL_STORAGE_QOS_CONTROL (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x0350 | FSCTL_METHOD_BUFFERED) +#define FSCTL_SVHDX_SYNC_TUNNEL_REQUEST (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x0304 | FSCTL_METHOD_BUFFERED) +#define FSCTL_QUERY_SHARED_VIRTUAL_DISK_SUPPORT (FSCTL_FILESYSTEM | FSCTL_ACCESS_ANY | 0x0300 | FSCTL_METHOD_BUFFERED) + +#define FSCTL_NAMED_PIPE 0x00110000 +#define FSCTL_PIPE_PEEK (FSCTL_NAMED_PIPE | FSCTL_ACCESS_READ | 0x000C | FSCTL_METHOD_BUFFERED) +#define FSCTL_NAMED_PIPE_READ_WRITE (FSCTL_NAMED_PIPE | FSCTL_ACCESS_READ \ + | FSCTL_ACCESS_WRITE | 0x0014 | FSCTL_METHOD_NEITHER) +#define FSCTL_PIPE_TRANSCEIVE FSCTL_NAMED_PIPE_READ_WRITE /* SMB2 function name */ +#define FSCTL_PIPE_WAIT (FSCTL_NAMED_PIPE | FSCTL_ACCESS_ANY | 0x0018 | FSCTL_METHOD_BUFFERED) + +#define FSCTL_NETWORK_FILESYSTEM 0x00140000 +#define FSCTL_GET_SHADOW_COPY_DATA (FSCTL_NETWORK_FILESYSTEM | FSCTL_ACCESS_READ | 0x0064 | FSCTL_METHOD_BUFFERED) +#define FSCTL_SRV_ENUM_SNAPS FSCTL_GET_SHADOW_COPY_DATA /* SMB2 function name */ +#define FSCTL_SRV_REQUEST_RESUME_KEY (FSCTL_NETWORK_FILESYSTEM | FSCTL_ACCESS_ANY | 0x0078 | FSCTL_METHOD_BUFFERED) +#define FSCTL_SRV_COPYCHUNK (FSCTL_NETWORK_FILESYSTEM | FSCTL_ACCESS_READ | 0x00F0 | FSCTL_METHOD_OUT_DIRECT) +#define FSCTL_SRV_COPYCHUNK_WRITE (FSCTL_NETWORK_FILESYSTEM | FSCTL_ACCESS_WRITE | 0x00F0 | FSCTL_METHOD_OUT_DIRECT) +#define FSCTL_SRV_READ_HASH (FSCTL_NETWORK_FILESYSTEM | FSCTL_ACCESS_READ| 0x01B8 | FSCTL_METHOD_NEITHER) +#define FSCTL_LMR_REQ_RESILIENCY (FSCTL_NETWORK_FILESYSTEM | FSCTL_ACCESS_ANY | 0x01D4 | FSCTL_METHOD_BUFFERED) +#define FSCTL_LMR_SET_LINK_TRACKING_INFORMATION \ + (FSCTL_NETWORK_FILESYSTEM | FSCTL_ACCESS_ANY | 0x00EC | FSCTL_METHOD_BUFFERED) +#define FSCTL_QUERY_NETWORK_INTERFACE_INFO \ + (FSCTL_NETWORK_FILESYSTEM | FSCTL_ACCESS_ANY | 0x01FC | FSCTL_METHOD_BUFFERED) + +/* + * FSCTL_VALIDATE_NEGOTIATE_INFO_224 was used used in + * Windows 8 server beta with SMB 2.24 + */ +#define FSCTL_VALIDATE_NEGOTIATE_INFO_224 \ + (FSCTL_NETWORK_FILESYSTEM | FSCTL_ACCESS_ANY | 0x0200 | FSCTL_METHOD_BUFFERED) +#define FSCTL_VALIDATE_NEGOTIATE_INFO (FSCTL_NETWORK_FILESYSTEM | FSCTL_ACCESS_ANY | 0x0204 | FSCTL_METHOD_BUFFERED) + +/* + * For testing various details we use special codes via + * smbtorture in order to test failures + */ +#define FSCTL_SMBTORTURE 0x83840000 +#define FSCTL_SMBTORTURE_FORCE_UNACKED_TIMEOUT \ + (FSCTL_SMBTORTURE | FSCTL_ACCESS_WRITE | 0x0000 | FSCTL_METHOD_NEITHER) +#define FSCTL_SMBTORTURE_IOCTL_RESPONSE_BODY_PADDING8 \ + (FSCTL_SMBTORTURE | FSCTL_ACCESS_WRITE | 0x0010 | FSCTL_METHOD_NEITHER) +#define FSCTL_SMBTORTURE_GLOBAL_READ_RESPONSE_BODY_PADDING8 \ + (FSCTL_SMBTORTURE | FSCTL_ACCESS_WRITE | 0x0020 | FSCTL_METHOD_NEITHER) +#define FSCTL_SMBTORTURE_FSP_ASYNC_SLEEP \ + (FSCTL_SMBTORTURE | FSCTL_ACCESS_WRITE | 0x0040 | FSCTL_METHOD_NEITHER) + +/* + * A few values from [MS-FSCC] 2.1.2.1 Reparse Tags + */ + +#define IO_REPARSE_TAG_SYMLINK 0xA000000C +#define IO_REPARSE_TAG_MOUNT_POINT 0xA0000003 +#define IO_REPARSE_TAG_HSM 0xC0000004 +#define IO_REPARSE_TAG_SIS 0x80000007 +#define IO_REPARSE_TAG_DFS 0x8000000A +#define IO_REPARSE_TAG_NFS 0x80000014 + +/* + * Flag from [MS-FSCC] 2.1.2.4 Symbolic Link Reparse Data Buffer + */ +#define SYMLINK_FLAG_RELATIVE 0x00000001 + +#endif /* _SMB_CONSTANTS_H */ diff --git a/libcli/smb/smb_seal.c b/libcli/smb/smb_seal.c new file mode 100644 index 0000000..079744c --- /dev/null +++ b/libcli/smb/smb_seal.c @@ -0,0 +1,220 @@ +/* + Unix SMB/CIFS implementation. + SMB Transport encryption (sealing) code. + Copyright (C) Jeremy Allison 2007. + + 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 "smb_common.h" +#ifdef HAVE_KRB5 +#include "lib/krb5_wrap/krb5_samba.h" +#endif +#include "auth/gensec/gensec.h" +#include "libcli/smb/smb_seal.h" + +#undef malloc + +/****************************************************************************** + Pull out the encryption context for this packet. 0 means global context. +******************************************************************************/ + +NTSTATUS get_enc_ctx_num(const uint8_t *buf, uint16_t *p_enc_ctx_num) +{ + if (smb_len_nbt(buf) < 8) { + return NT_STATUS_INVALID_BUFFER_SIZE; + } + + if (buf[4] == 0xFF) { + if (buf[5] == 'S' && buf [6] == 'M' && buf[7] == 'B') { + /* Not an encrypted buffer. */ + return NT_STATUS_NOT_FOUND; + } + if (buf[5] == 'E') { + *p_enc_ctx_num = SVAL(buf,6); + return NT_STATUS_OK; + } + } + return NT_STATUS_INVALID_NETWORK_RESPONSE; +} + +/******************************************************************* + Set the length and marker of an encrypted smb packet. +********************************************************************/ + +static void smb_set_enclen(char *buf,int len,uint16_t enc_ctx_num) +{ + _smb_setlen_tcp(buf,len); + + SCVAL(buf,4,0xFF); + SCVAL(buf,5,'E'); + SSVAL(buf,6,enc_ctx_num); +} + +/****************************************************************************** + Generic code for client and server. + Is encryption turned on ? +******************************************************************************/ + +bool common_encryption_on(struct smb_trans_enc_state *es) +{ + return ((es != NULL) && es->enc_on); +} + +/****************************************************************************** + Generic code for client and server. + GENSEC decrypt an incoming buffer. +******************************************************************************/ + +static NTSTATUS common_gensec_decrypt_buffer(struct gensec_security *gensec, + char *buf) +{ + NTSTATUS status; + size_t buf_len = smb_len_nbt(buf) + 4; /* Don't forget the 4 length bytes. */ + DATA_BLOB in_buf, out_buf; + TALLOC_CTX *frame; + + if (buf_len < 8) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + + frame = talloc_stackframe(); + + in_buf = data_blob_const(buf + 8, buf_len - 8); + + status = gensec_unwrap(gensec, frame, &in_buf, &out_buf); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("common_gensec_decrypt_buffer: gensec_unwrap failed. Error %s\n", + nt_errstr(status))); + TALLOC_FREE(frame); + return status; + } + + if (out_buf.length > in_buf.length) { + DEBUG(0,("common_gensec_decrypt_buffer: gensec_unwrap size (%u) too large (%u) !\n", + (unsigned int)out_buf.length, + (unsigned int)in_buf.length )); + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + + memcpy(buf + 8, out_buf.data, out_buf.length); + + /* Reset the length and overwrite the header. */ + smb_setlen_nbt(buf, out_buf.length + 4); + + TALLOC_FREE(frame); + + return NT_STATUS_OK; +} + +/****************************************************************************** + Generic code for client and server. + NTLM encrypt an outgoing buffer. Return the encrypted pointer in ppbuf_out. +******************************************************************************/ + +static NTSTATUS common_gensec_encrypt_buffer(struct gensec_security *gensec, + uint16_t enc_ctx_num, + char *buf, + char **ppbuf_out) +{ + NTSTATUS status; + DATA_BLOB in_buf, out_buf; + size_t buf_len = smb_len_nbt(buf) + 4; /* Don't forget the 4 length bytes. */ + TALLOC_CTX *frame; + + *ppbuf_out = NULL; + + if (buf_len < 8) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + in_buf = data_blob_const(buf + 8, buf_len - 8); + + frame = talloc_stackframe(); + + status = gensec_wrap(gensec, frame, &in_buf, &out_buf); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("common_gensec_encrypt_buffer: gensec_wrap failed. Error %s\n", + nt_errstr(status))); + TALLOC_FREE(frame); + return status; + } + + *ppbuf_out = (char *)malloc(out_buf.length + 8); /* We know this can't wrap. */ + if (!*ppbuf_out) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + memcpy(*ppbuf_out+8, out_buf.data, out_buf.length); + smb_set_enclen(*ppbuf_out, out_buf.length + 4, enc_ctx_num); + + TALLOC_FREE(frame); + + return NT_STATUS_OK; +} + +/****************************************************************************** + Generic code for client and server. + Encrypt an outgoing buffer. Return the alloced encrypted pointer in buf_out. +******************************************************************************/ + +NTSTATUS common_encrypt_buffer(struct smb_trans_enc_state *es, char *buffer, char **buf_out) +{ + if (!common_encryption_on(es)) { + /* Not encrypting. */ + *buf_out = buffer; + return NT_STATUS_OK; + } + + return common_gensec_encrypt_buffer(es->gensec_security, es->enc_ctx_num, buffer, buf_out); +} + +/****************************************************************************** + Generic code for client and server. + Decrypt an incoming SMB buffer. Replaces the data within it. + New data must be less than or equal to the current length. +******************************************************************************/ + +NTSTATUS common_decrypt_buffer(struct smb_trans_enc_state *es, char *buf) +{ + if (!common_encryption_on(es)) { + /* Not decrypting. */ + return NT_STATUS_OK; + } + + return common_gensec_decrypt_buffer(es->gensec_security, buf); +} + +/****************************************************************************** + Free an encryption-allocated buffer. +******************************************************************************/ + +void common_free_enc_buffer(struct smb_trans_enc_state *es, char *buf) +{ + uint16_t enc_ctx_num; + + if (!common_encryption_on(es)) { + return; + } + + if (!NT_STATUS_IS_OK(get_enc_ctx_num((const uint8_t *)buf, + &enc_ctx_num))) { + return; + } + + SAFE_FREE(buf); +} diff --git a/libcli/smb/smb_seal.h b/libcli/smb/smb_seal.h new file mode 100644 index 0000000..f47f904 --- /dev/null +++ b/libcli/smb/smb_seal.h @@ -0,0 +1,37 @@ +/* + Unix SMB/CIFS implementation. + SMB Transport encryption code. + Copyright (C) Jeremy Allison 2007. + + 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 _HEADER_SMB_CRYPT_H +#define _HEADER_SMB_CRYPT_H + +struct smb_trans_enc_state { + uint16_t enc_ctx_num; + bool enc_on; + struct gensec_security *gensec_security; +}; + +/* The following definitions come from smb_seal.c */ + +NTSTATUS get_enc_ctx_num(const uint8_t *buf, uint16_t *p_enc_ctx_num); +bool common_encryption_on(struct smb_trans_enc_state *es); +NTSTATUS common_encrypt_buffer(struct smb_trans_enc_state *es, char *buffer, char **buf_out); +NTSTATUS common_decrypt_buffer(struct smb_trans_enc_state *es, char *buf); +void common_free_enc_buffer(struct smb_trans_enc_state *es, char *buf); + +#endif /* _HEADER_SMB_CRYPT_H */ diff --git a/libcli/smb/smb_signing.c b/libcli/smb/smb_signing.c new file mode 100644 index 0000000..1d768ef --- /dev/null +++ b/libcli/smb/smb_signing.c @@ -0,0 +1,552 @@ +/* + Unix SMB/CIFS implementation. + SMB Signing Code + Copyright (C) Jeremy Allison 2003. + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2002-2003 + Copyright (C) Stefan Metzmacher 2009 + + 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 "smb_common.h" +#include "smb_signing.h" + +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +/* Used by the SMB1 signing functions. */ + +struct smb1_signing_state { + /* is signing localy allowed */ + bool allowed; + + /* is signing localy desired */ + bool desired; + + /* is signing localy mandatory */ + bool mandatory; + + /* is signing negotiated by the peer */ + bool negotiated; + + bool active; /* Have I ever seen a validly signed packet? */ + + /* mac_key.length > 0 means signing is started */ + DATA_BLOB mac_key; + + /* the next expected seqnum */ + uint32_t seqnum; + + TALLOC_CTX *mem_ctx; + void *(*alloc_fn)(TALLOC_CTX *mem_ctx, size_t len); + void (*free_fn)(TALLOC_CTX *mem_ctx, void *ptr); +}; + +static void smb1_signing_reset_info(struct smb1_signing_state *si) +{ + si->active = false; + si->seqnum = 0; + + if (si->free_fn) { + si->free_fn(si->mem_ctx, si->mac_key.data); + } else { + talloc_free(si->mac_key.data); + } + si->mac_key.data = NULL; + si->mac_key.length = 0; +} + +struct smb1_signing_state *smb1_signing_init_ex(TALLOC_CTX *mem_ctx, + bool allowed, + bool desired, + bool mandatory, + void *(*alloc_fn)(TALLOC_CTX *, size_t), + void (*free_fn)(TALLOC_CTX *, void *)) +{ + struct smb1_signing_state *si; + + if (alloc_fn) { + void *p = alloc_fn(mem_ctx, sizeof(struct smb1_signing_state)); + if (p == NULL) { + return NULL; + } + memset(p, 0, sizeof(struct smb1_signing_state)); + si = (struct smb1_signing_state *)p; + si->mem_ctx = mem_ctx; + si->alloc_fn = alloc_fn; + si->free_fn = free_fn; + } else { + si = talloc_zero(mem_ctx, struct smb1_signing_state); + if (si == NULL) { + return NULL; + } + } + + if (mandatory) { + desired = true; + } + + if (desired) { + allowed = true; + } + + si->allowed = allowed; + si->desired = desired; + si->mandatory = mandatory; + + return si; +} + +struct smb1_signing_state *smb1_signing_init(TALLOC_CTX *mem_ctx, + bool allowed, + bool desired, + bool mandatory) +{ + return smb1_signing_init_ex(mem_ctx, allowed, desired, mandatory, + NULL, NULL); +} + +static bool smb1_signing_good(struct smb1_signing_state *si, + bool good, uint32_t seq) +{ + if (good) { + if (!si->active) { + si->active = true; + } + return true; + } + + if (!si->mandatory && !si->active) { + /* Non-mandatory signing - just turn off if this is the first bad packet.. */ + DBG_INFO("signing negotiated but not required and peer\n" + "isn't sending correct signatures. Turning off.\n"); + smb1_signing_reset_info(si); + return true; + } + + /* Mandatory signing or bad packet after signing started - fail and disconnect. */ + DBG_ERR("BAD SIG: seq %u\n", (unsigned int)seq); + return false; +} + +static NTSTATUS smb1_signing_md5(const DATA_BLOB *mac_key, + const uint8_t *hdr, size_t len, + uint32_t seq_number, + uint8_t calc_md5_mac[16]) +{ + const size_t offset_end_of_sig = (HDR_SS_FIELD + 8); + uint8_t sequence_buf[8]; + gnutls_hash_hd_t hash_hnd = NULL; + int rc; + + /* + * Firstly put the sequence number into the first 4 bytes. + * and zero out the next 4 bytes. + * + * We do this here, to avoid modifying the packet. + */ + + DBG_DEBUG("sequence number %u\n", seq_number ); + + SIVAL(sequence_buf, 0, seq_number); + SIVAL(sequence_buf, 4, 0); + + /* + * Calculate the 16 byte MAC - but don't alter the data in the + * incoming packet. + * + * This makes for a bit of fussing about, but it's not too bad. + */ + rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_MD5); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED); + } + /* Initialise with the key. */ + rc = gnutls_hash(hash_hnd, mac_key->data, mac_key->length); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED); + } + /* Copy in the first bit of the SMB header. */ + rc = gnutls_hash(hash_hnd, hdr, HDR_SS_FIELD); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED); + } + /* Copy in the sequence number, instead of the signature. */ + rc = gnutls_hash(hash_hnd, sequence_buf, sizeof(sequence_buf)); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED); + } + /* Copy in the rest of the packet in, skipping the signature. */ + rc = gnutls_hash(hash_hnd, hdr + offset_end_of_sig, len - offset_end_of_sig); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED); + } + + gnutls_hash_deinit(hash_hnd, calc_md5_mac); + + return NT_STATUS_OK; +} + +uint32_t smb1_signing_next_seqnum(struct smb1_signing_state *si, bool oneway) +{ + uint32_t seqnum; + + if (si->mac_key.length == 0) { + return 0; + } + + seqnum = si->seqnum; + if (oneway) { + si->seqnum += 1; + } else { + si->seqnum += 2; + } + + return seqnum; +} + +void smb1_signing_cancel_reply(struct smb1_signing_state *si, bool oneway) +{ + if (si->mac_key.length == 0) { + return; + } + + if (oneway) { + si->seqnum -= 1; + } else { + si->seqnum -= 2; + } +} + +NTSTATUS smb1_signing_sign_pdu(struct smb1_signing_state *si, + uint8_t *outhdr, size_t len, + uint32_t seqnum) +{ + uint8_t calc_md5_mac[16]; + uint8_t com; + uint8_t flags; + + if (si->mac_key.length == 0) { + if (!si->negotiated) { + return NT_STATUS_OK; + } + } + + /* JRA Paranioa test - we should be able to get rid of this... */ + if (len < (HDR_SS_FIELD + 8)) { + DBG_WARNING("Logic error. " + "Can't check signature on short packet! smb_len = %u\n", + (unsigned)len); + abort(); + } + + com = SVAL(outhdr, HDR_COM); + flags = SVAL(outhdr, HDR_FLG); + + if (!(flags & FLAG_REPLY)) { + uint16_t flags2 = SVAL(outhdr, HDR_FLG2); + /* + * If this is a request, specify what is + * supported or required by the client + */ + if (si->negotiated && si->desired) { + flags2 |= FLAGS2_SMB_SECURITY_SIGNATURES; + } + if (si->negotiated && si->mandatory) { + flags2 |= FLAGS2_SMB_SECURITY_SIGNATURES_REQUIRED; + } + SSVAL(outhdr, HDR_FLG2, flags2); + } + + if (si->mac_key.length == 0) { + /* I wonder what BSRSPYL stands for - but this is what MS + actually sends! */ + if (com == SMBsesssetupX) { + memcpy(calc_md5_mac, "BSRSPYL ", 8); + } else { + memset(calc_md5_mac, 0, 8); + } + } else { + NTSTATUS status; + + status = smb1_signing_md5(&si->mac_key, + outhdr, + len, + seqnum, + calc_md5_mac); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + DBG_DEBUG("sent SMB signature of\n"); + dump_data(10, calc_md5_mac, 8); + + memcpy(&outhdr[HDR_SS_FIELD], calc_md5_mac, 8); + +/* outhdr[HDR_SS_FIELD+2]=0; + Uncomment this to test if the remote server actually verifies signatures...*/ + + return NT_STATUS_OK; +} + +bool smb1_signing_check_pdu(struct smb1_signing_state *si, + const uint8_t *inhdr, size_t len, + uint32_t seqnum) +{ + bool good; + uint8_t calc_md5_mac[16]; + const uint8_t *reply_sent_mac; + NTSTATUS status; + + if (si->mac_key.length == 0) { + return true; + } + + if (len < (HDR_SS_FIELD + 8)) { + DBG_WARNING("Can't check signature " + "on short packet! smb_len = %u\n", + (unsigned)len); + return false; + } + + status = smb1_signing_md5(&si->mac_key, + inhdr, + len, + seqnum, + calc_md5_mac); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Failed to calculate signing mac: %s\n", + nt_errstr(status)); + return false; + } + + reply_sent_mac = &inhdr[HDR_SS_FIELD]; + good = mem_equal_const_time(reply_sent_mac, calc_md5_mac, 8); + + if (!good) { + int i; + const int sign_range = 5; + + DBG_INFO("BAD SIG: wanted SMB signature of\n"); + dump_data(5, calc_md5_mac, 8); + + DBG_INFO("BAD SIG: got SMB signature of\n"); + dump_data(5, reply_sent_mac, 8); + + for (i = -sign_range; i < sign_range; i++) { + smb1_signing_md5(&si->mac_key, inhdr, len, + seqnum+i, calc_md5_mac); + if (mem_equal_const_time(reply_sent_mac, calc_md5_mac, 8)) { + DBG_ERR("out of seq. seq num %u matches. " + "We were expecting seq %u\n", + (unsigned int)seqnum+i, + (unsigned int)seqnum); + break; + } + } + } else { + DBG_DEBUG("seq %u: got good SMB signature of\n", + (unsigned int)seqnum); + dump_data(10, reply_sent_mac, 8); + } + + return smb1_signing_good(si, good, seqnum); +} + +bool smb1_signing_activate(struct smb1_signing_state *si, + const DATA_BLOB user_session_key, + const DATA_BLOB response) +{ + size_t len; + off_t ofs; + + if (!user_session_key.length) { + return false; + } + + if (!si->negotiated) { + return false; + } + + if (si->active) { + return false; + } + + if (si->mac_key.length > 0) { + return false; + } + + smb1_signing_reset_info(si); + + len = response.length + user_session_key.length; + if (si->alloc_fn) { + si->mac_key.data = (uint8_t *)si->alloc_fn(si->mem_ctx, len); + if (si->mac_key.data == NULL) { + return false; + } + } else { + si->mac_key.data = (uint8_t *)talloc_size(si, len); + if (si->mac_key.data == NULL) { + return false; + } + } + si->mac_key.length = len; + + ofs = 0; + memcpy(&si->mac_key.data[ofs], user_session_key.data, user_session_key.length); + + DBG_DEBUG("user_session_key\n"); + dump_data(10, user_session_key.data, user_session_key.length); + + if (response.length) { + ofs = user_session_key.length; + memcpy(&si->mac_key.data[ofs], response.data, response.length); + DBG_DEBUG("response_data\n"); + dump_data(10, response.data, response.length); + } else { + DBG_DEBUG("NULL response_data\n"); + } + + dump_data_pw("smb1_signing_activate: mac key is:\n", + si->mac_key.data, si->mac_key.length); + + /* Initialise the sequence number */ + si->seqnum = 2; + + return true; +} + +bool smb1_signing_is_active(struct smb1_signing_state *si) +{ + return si->active; +} + +bool smb1_signing_is_desired(struct smb1_signing_state *si) +{ + return si->desired; +} + +bool smb1_signing_is_mandatory(struct smb1_signing_state *si) +{ + return si->mandatory; +} + +bool smb1_signing_set_negotiated(struct smb1_signing_state *si, + bool allowed, bool mandatory) +{ + if (si->active) { + return true; + } + + if (mandatory) { + allowed = true; + } + + if (!si->allowed && mandatory) { + return false; + } + + if (si->mandatory && !allowed) { + return false; + } + + if (si->mandatory) { + si->negotiated = true; + return true; + } + + if (mandatory) { + si->negotiated = true; + return true; + } + + if (!si->desired) { + si->negotiated = false; + return true; + } + + if (si->desired && allowed) { + si->negotiated = true; + return true; + } + + si->negotiated = false; + return true; +} + +bool smb1_signing_is_negotiated(struct smb1_signing_state *si) +{ + return si->negotiated; +} + +NTSTATUS smb1_key_derivation(const uint8_t *KI, + size_t KI_len, + uint8_t KO[16]) +{ + int rc; + static const uint8_t SSKeyHash[256] = { + 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, + 0x20, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x20, 0x4b, 0x65, 0x79, 0x20, 0x55, + 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x79, 0x07, + 0x6e, 0x28, 0x2e, 0x69, 0x88, 0x10, 0xb3, 0xdb, + 0x01, 0x55, 0x72, 0xfb, 0x74, 0x14, 0xfb, 0xc4, + 0xc5, 0xaf, 0x3b, 0x41, 0x65, 0x32, 0x17, 0xba, + 0xa3, 0x29, 0x08, 0xc1, 0xde, 0x16, 0x61, 0x7e, + 0x66, 0x98, 0xa4, 0x0b, 0xfe, 0x06, 0x83, 0x53, + 0x4d, 0x05, 0xdf, 0x6d, 0xa7, 0x51, 0x10, 0x73, + 0xc5, 0x50, 0xdc, 0x5e, 0xf8, 0x21, 0x46, 0xaa, + 0x96, 0x14, 0x33, 0xd7, 0x52, 0xeb, 0xaf, 0x1f, + 0xbf, 0x36, 0x6c, 0xfc, 0xb7, 0x1d, 0x21, 0x19, + 0x81, 0xd0, 0x6b, 0xfa, 0x77, 0xad, 0xbe, 0x18, + 0x78, 0xcf, 0x10, 0xbd, 0xd8, 0x78, 0xf7, 0xd3, + 0xc6, 0xdf, 0x43, 0x32, 0x19, 0xd3, 0x9b, 0xa8, + 0x4d, 0x9e, 0xaa, 0x41, 0xaf, 0xcb, 0xc6, 0xb9, + 0x34, 0xe7, 0x48, 0x25, 0xd4, 0x88, 0xc4, 0x51, + 0x60, 0x38, 0xd9, 0x62, 0xe8, 0x8d, 0x5b, 0x83, + 0x92, 0x7f, 0xb5, 0x0e, 0x1c, 0x2d, 0x06, 0x91, + 0xc3, 0x75, 0xb3, 0xcc, 0xf8, 0xf7, 0x92, 0x91, + 0x0b, 0x3d, 0xa1, 0x10, 0x5b, 0xd5, 0x0f, 0xa8, + 0x3f, 0x5d, 0x13, 0x83, 0x0a, 0x6b, 0x72, 0x93, + 0x14, 0x59, 0xd5, 0xab, 0xde, 0x26, 0x15, 0x6d, + 0x60, 0x67, 0x71, 0x06, 0x6e, 0x3d, 0x0d, 0xa7, + 0xcb, 0x70, 0xe9, 0x08, 0x5c, 0x99, 0xfa, 0x0a, + 0x5f, 0x3d, 0x44, 0xa3, 0x8b, 0xc0, 0x8d, 0xda, + 0xe2, 0x68, 0xd0, 0x0d, 0xcd, 0x7f, 0x3d, 0xf8, + 0x73, 0x7e, 0x35, 0x7f, 0x07, 0x02, 0x0a, 0xb5, + 0xe9, 0xb7, 0x87, 0xfb, 0xa1, 0xbf, 0xcb, 0x32, + 0x31, 0x66, 0x09, 0x48, 0x88, 0xcc, 0x18, 0xa3, + 0xb2, 0x1f, 0x1f, 0x1b, 0x90, 0x4e, 0xd7, 0xe1 + }; + + /* The callers passing down KI_len of 16 so no need to limit to 64 */ + rc = gnutls_hmac_fast(GNUTLS_MAC_MD5, + KI, + KI_len, + SSKeyHash, + sizeof(SSKeyHash), + KO); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + + return NT_STATUS_OK; +} diff --git a/libcli/smb/smb_signing.h b/libcli/smb/smb_signing.h new file mode 100644 index 0000000..9f2f3c1 --- /dev/null +++ b/libcli/smb/smb_signing.h @@ -0,0 +1,58 @@ +/* + Unix SMB/CIFS implementation. + SMB Signing Code + Copyright (C) Jeremy Allison 2003. + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2002-2003 + Copyright (C) Stefan Metzmacher 2009 + + 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 _SMB_SIGNING_H_ +#define _SMB_SIGNING_H_ + +struct smb1_signing_state; + +struct smb1_signing_state *smb1_signing_init(TALLOC_CTX *mem_ctx, + bool allowed, + bool desired, + bool mandatory); +struct smb1_signing_state *smb1_signing_init_ex(TALLOC_CTX *mem_ctx, + bool allowed, + bool desired, + bool mandatory, + void *(*alloc_fn)(TALLOC_CTX *, size_t), + void (*free_fn)(TALLOC_CTX *, void *)); +uint32_t smb1_signing_next_seqnum(struct smb1_signing_state *si, bool oneway); +void smb1_signing_cancel_reply(struct smb1_signing_state *si, bool oneway); +NTSTATUS smb1_signing_sign_pdu(struct smb1_signing_state *si, + uint8_t *outhdr, size_t len, + uint32_t seqnum); +bool smb1_signing_check_pdu(struct smb1_signing_state *si, + const uint8_t *inhdr, size_t len, + uint32_t seqnum); +bool smb1_signing_activate(struct smb1_signing_state *si, + const DATA_BLOB user_session_key, + const DATA_BLOB response); +bool smb1_signing_is_active(struct smb1_signing_state *si); +bool smb1_signing_is_desired(struct smb1_signing_state *si); +bool smb1_signing_is_mandatory(struct smb1_signing_state *si); +bool smb1_signing_set_negotiated(struct smb1_signing_state *si, + bool allowed, bool mandatory); +bool smb1_signing_is_negotiated(struct smb1_signing_state *si); +NTSTATUS smb1_key_derivation(const uint8_t *KI, + size_t KI_len, + uint8_t KO[16]); + +#endif /* _SMB_SIGNING_H_ */ diff --git a/libcli/smb/smb_unix_ext.h b/libcli/smb/smb_unix_ext.h new file mode 100644 index 0000000..e74976b --- /dev/null +++ b/libcli/smb/smb_unix_ext.h @@ -0,0 +1,456 @@ +/* + Unix SMB/CIFS implementation. + SMB transaction2 handling + + Copyright (C) James Peach 2007 + Copyright (C) Jeremy Allison 1994-2002. + + Extensively modified by Andrew Tridgell, 1995 + + 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 __SMB_UNIX_EXT_H__ +#define __SMB_UNIX_EXT_H__ + +/* UNIX CIFS Extensions - created by HP */ +/* + * UNIX CIFS Extensions have the range 0x200 - 0x2FF reserved. + * Supposedly Microsoft have agreed to this. + */ + +#define MIN_UNIX_INFO_LEVEL 0x200 +#define MAX_UNIX_INFO_LEVEL 0x2FF + +#define INFO_LEVEL_IS_UNIX(level) (((level) >= MIN_UNIX_INFO_LEVEL) && ((level) <= MAX_UNIX_INFO_LEVEL)) + +#define SMB_QUERY_FILE_UNIX_BASIC 0x200 /* UNIX File Info*/ +#define SMB_SET_FILE_UNIX_BASIC 0x200 +#define SMB_SET_FILE_UNIX_INFO2 0x20B /* UNIX File Info2 */ + +#define SMB_MODE_NO_CHANGE 0xFFFFFFFF /* file mode value which */ + /* means "don't change it" */ +#define SMB_UID_NO_CHANGE 0xFFFFFFFF +#define SMB_GID_NO_CHANGE 0xFFFFFFFF + +#define SMB_SIZE_NO_CHANGE_LO 0xFFFFFFFF +#define SMB_SIZE_NO_CHANGE_HI 0xFFFFFFFF + +#define SMB_TIME_NO_CHANGE_LO 0xFFFFFFFF +#define SMB_TIME_NO_CHANGE_HI 0xFFFFFFFF + +/* +Offset Size Name +0 LARGE_INTEGER EndOfFile File size +8 LARGE_INTEGER Blocks Number of bytes used on disk (st_blocks). +16 LARGE_INTEGER CreationTime Creation time +24 LARGE_INTEGER LastAccessTime Last access time +32 LARGE_INTEGER LastModificationTime Last modification time +40 LARGE_INTEGER Uid Numeric user id for the owner +48 LARGE_INTEGER Gid Numeric group id of owner +56 ULONG Type Enumeration specifying the pathname type: + 0 -- File + 1 -- Directory + 2 -- Symbolic link + 3 -- Character device + 4 -- Block device + 5 -- FIFO (named pipe) + 6 -- Unix domain socket + +60 LARGE_INTEGER devmajor Major device number if type is device +68 LARGE_INTEGER devminor Minor device number if type is device +76 LARGE_INTEGER uniqueid This is a server-assigned unique id for the file. The client + will typically map this onto an inode number. The scope of + uniqueness is the share. +84 LARGE_INTEGER permissions Standard UNIX file permissions - see below. +92 LARGE_INTEGER nlinks The number of directory entries that map to this entry + (number of hard links) + +100 - end. +*/ + +#define SMB_FILE_UNIX_BASIC_SIZE 100 + +/* UNIX filetype mappings. */ + +#define UNIX_TYPE_FILE 0 +#define UNIX_TYPE_DIR 1 +#define UNIX_TYPE_SYMLINK 2 +#define UNIX_TYPE_CHARDEV 3 +#define UNIX_TYPE_BLKDEV 4 +#define UNIX_TYPE_FIFO 5 +#define UNIX_TYPE_SOCKET 6 +#define UNIX_TYPE_UNKNOWN 0xFFFFFFFF + +/* + * Oh this is fun. "Standard UNIX permissions" has no + * meaning in POSIX. We need to define the mapping onto + * and off the wire as this was not done in the original HP + * spec. JRA. + */ + +#define UNIX_X_OTH 0000001 +#define UNIX_W_OTH 0000002 +#define UNIX_R_OTH 0000004 +#define UNIX_X_GRP 0000010 +#define UNIX_W_GRP 0000020 +#define UNIX_R_GRP 0000040 +#define UNIX_X_USR 0000100 +#define UNIX_W_USR 0000200 +#define UNIX_R_USR 0000400 +#define UNIX_STICKY 0001000 +#define UNIX_SET_GID 0002000 +#define UNIX_SET_UID 0004000 + +/* Masks for the above */ +#define UNIX_OTH_MASK 0000007 +#define UNIX_GRP_MASK 0000070 +#define UNIX_USR_MASK 0000700 +#define UNIX_PERM_MASK 0000777 +#define UNIX_EXTRA_MASK 0007000 +#define UNIX_ALL_MASK 0007777 + +/* Flags for chflags (CIFS_UNIX_EXTATTR_CAP capability) and + * SMB_QUERY_FILE_UNIX_INFO2. + */ +#define EXT_SECURE_DELETE 0x00000001 +#define EXT_ENABLE_UNDELETE 0x00000002 +#define EXT_SYNCHRONOUS 0x00000004 +#define EXT_IMMUTABLE 0x00000008 +#define EXT_OPEN_APPEND_ONLY 0x00000010 +#define EXT_DO_NOT_BACKUP 0x00000020 +#define EXT_NO_UPDATE_ATIME 0x00000040 +#define EXT_HIDDEN 0x00000080 + +#define SMB_QUERY_FILE_UNIX_LINK 0x201 +#define SMB_SET_FILE_UNIX_LINK 0x201 +#define SMB_SET_FILE_UNIX_HLINK 0x203 +/* SMB_QUERY_POSIX_ACL 0x204 see below */ +#define SMB_QUERY_XATTR 0x205 /* need for non-user XATTRs */ +#define SMB_QUERY_ATTR_FLAGS 0x206 /* chflags, chattr */ +#define SMB_SET_ATTR_FLAGS 0x206 +#define SMB_QUERY_POSIX_PERMISSION 0x207 +/* Only valid for qfileinfo */ +#define SMB_QUERY_POSIX_LOCK 0x208 +/* Only valid for setfileinfo */ +#define SMB_SET_POSIX_LOCK 0x208 + +/* The set info levels for POSIX path operations. */ +#define SMB_POSIX_PATH_OPEN 0x209 +#define SMB_POSIX_PATH_UNLINK 0x20A + +#define SMB_QUERY_FILE_UNIX_INFO2 0x20B /* UNIX File Info2 */ +#define SMB_SET_FILE_UNIX_INFO2 0x20B + +/* +SMB_QUERY_FILE_UNIX_INFO2 is SMB_QUERY_FILE_UNIX_BASIC with create +time and file flags appended. The corresponding info level for +findfirst/findnext is SMB_FIND_FILE_UNIX_INFO2. + Size Offset Value + --------------------- + 0 LARGE_INTEGER EndOfFile File size + 8 LARGE_INTEGER Blocks Number of blocks used on disk + 16 LARGE_INTEGER ChangeTime Attribute change time + 24 LARGE_INTEGER LastAccessTime Last access time + 32 LARGE_INTEGER LastModificationTime Last modification time + 40 LARGE_INTEGER Uid Numeric user id for the owner + 48 LARGE_INTEGER Gid Numeric group id of owner + 56 ULONG Type Enumeration specifying the file type + 60 LARGE_INTEGER devmajor Major device number if type is device + 68 LARGE_INTEGER devminor Minor device number if type is device + 76 LARGE_INTEGER uniqueid This is a server-assigned unique id + 84 LARGE_INTEGER permissions Standard UNIX permissions + 92 LARGE_INTEGER nlinks Number of hard links + 100 LARGE_INTEGER CreationTime Create/birth time + 108 ULONG FileFlags File flags enumeration + 112 ULONG FileFlagsMask Mask of valid flags +*/ + +/* Transact 2 Find First levels */ +#define SMB_FIND_FILE_UNIX 0x202 +#define SMB_FIND_FILE_UNIX_INFO2 0x20B /* UNIX File Info2 */ + +#define SMB_FILE_UNIX_INFO2_SIZE 116 + +/* + Info level for TRANS2_QFSINFO - returns version of CIFS UNIX extensions, plus + 64-bits worth of capability fun :-). + Use the same info level for TRANS2_SETFSINFO +*/ + +#define SMB_QUERY_CIFS_UNIX_INFO 0x200 +#define SMB_SET_CIFS_UNIX_INFO 0x200 + +/* Returns or sets the following. + + UINT16 major version number + UINT16 minor version number + LARGE_INTEGER capability bitfield + +*/ + +#define CIFS_UNIX_MAJOR_VERSION 1 +#define CIFS_UNIX_MINOR_VERSION 0 + +#define CIFS_UNIX_FCNTL_LOCKS_CAP 0x1 +#define CIFS_UNIX_POSIX_ACLS_CAP 0x2 +#define CIFS_UNIX_XATTTR_CAP 0x4 /* for support of other xattr + namespaces such as system, + security and trusted */ +#define CIFS_UNIX_EXTATTR_CAP 0x8 /* for support of chattr + (chflags) and lsattr */ +#define CIFS_UNIX_POSIX_PATHNAMES_CAP 0x10 /* Use POSIX pathnames on the wire. */ +#define CIFS_UNIX_POSIX_PATH_OPERATIONS_CAP 0x20 /* We can cope with POSIX open/mkdir/unlink etc. */ +#define CIFS_UNIX_LARGE_READ_CAP 0x40 /* We can cope with 24 bit reads in readX. */ +#define CIFS_UNIX_LARGE_WRITE_CAP 0x80 /* We can cope with 24 bit writes in writeX. */ +#define CIFS_UNIX_TRANSPORT_ENCRYPTION_CAP 0x100 /* We can do SPNEGO negotiations for encryption. */ +#define CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP 0x200 /* We *must* SPNEGO negotiations for encryption. */ + +#define SMB_QUERY_POSIX_FS_INFO 0x201 + +/* Returns FILE_SYSTEM_POSIX_INFO struct as follows + (NB For undefined values return -1 in that field) + le32 OptimalTransferSize; bsize on some os, iosize on other os, This + is a hint to the client about best size. Server + can return -1 if no preference, ie if SMB + negotiated size is adequate for optimal + read/write performance + le32 BlockSize; (often 512 bytes) NB: BlockSize * TotalBlocks = disk space + le64 TotalBlocks; redundant with other infolevels but easy to ret here + le64 BlocksAvail; although redundant, easy to return + le64 UserBlocksAvail; bavail + le64 TotalFileNodes; + le64 FreeFileNodes; + le64 FileSysIdentifier; fsid + (NB statfs field Namelen comes from FILE_SYSTEM_ATTRIBUTE_INFO call) + (NB statfs field flags can come from FILE_SYSTEM_DEVICE_INFO call) +*/ + +#define SMB_QUERY_POSIX_WHO_AM_I 0x202 /* QFS Info */ +/* returns: + __u32 flags; 0 = Authenticated user 1 = GUEST + __u32 mask; which flags bits server understands ie 0x0001 + __u64 unix_user_id; + __u64 unix_user_gid; + __u32 number_of_supplementary_gids; may be zero + __u32 number_of_sids; may be zero + __u32 length_of_sid_array; in bytes - may be zero + __u32 pad; reserved - MBZ + __u64 gid_array[0]; may be empty + __u8 * psid_list may be empty +*/ + +/* ... more as we think of them :-). */ + +/* SMB POSIX ACL definitions. */ +/* Wire format is (all little endian) : + +[2 bytes] - Version number. +[2 bytes] - Number of ACE entries to follow. +[2 bytes] - Number of default ACE entries to follow. +------------------------------------- +^ +| +ACE entries +| +v +------------------------------------- +^ +| +Default ACE entries +| +v +------------------------------------- + +Where an ACE entry looks like : + +[1 byte] - Entry type. + +Entry types are : + +ACL_USER_OBJ 0x01 +ACL_USER 0x02 +ACL_GROUP_OBJ 0x04 +ACL_GROUP 0x08 +ACL_MASK 0x10 +ACL_OTHER 0x20 + +[1 byte] - permissions (perm_t) + +perm_t types are : + +ACL_READ 0x04 +ACL_WRITE 0x02 +ACL_EXECUTE 0x01 + +[8 bytes] - uid/gid to apply this permission to. + +In the same format as the uid/gid fields in the other +UNIX extensions definitions. Use 0xFFFFFFFFFFFFFFFF for +the MASK and OTHER entry types. + +If the Number of ACE entries for either file or default ACE's +is set to 0xFFFF this means ignore this kind of ACE (and the +number of entries sent will be zero. + +*/ + +#define SMB_QUERY_POSIX_WHOAMI 0x202 + +enum smb_whoami_flags { + SMB_WHOAMI_GUEST = 0x1 /* Logged in as (or squashed to) guest */ +}; + +/* Mask of which WHOAMI bits are valid. This should make it easier for clients + * to cope with servers that have different sets of WHOAMI flags (as more get + * added). + */ +#define SMB_WHOAMI_MASK 0x00000001 + +/* + SMBWhoami - Query the user mapping performed by the server for the + connected tree. This is a subcommand of the TRANS2_QFSINFO. + + Returns: + 4 bytes unsigned - mapping flags (smb_whoami_flags) + 4 bytes unsigned - flags mask + + 8 bytes unsigned - primary UID + 8 bytes unsigned - primary GID + 4 bytes unsigned - number of supplementary GIDs + 4 bytes unsigned - number of SIDs + 4 bytes unsigned - SID list byte count + 4 bytes - pad / reserved (must be zero) + + 8 bytes unsigned[] - list of GIDs (may be empty) + struct dom_sid[] - list of SIDs (may be empty) +*/ + +/* + * The following trans2 is done between client and server + * as a FSINFO call to set up the encryption state for transport + * encryption. + * This is a subcommand of the TRANS2_QFSINFO. + * + * The request looks like : + * + * [data block] -> SPNEGO framed GSSAPI request. + * + * The reply looks like : + * + * [data block] -> SPNEGO framed GSSAPI reply - if error + * is NT_STATUS_OK then we're done, if it's + * NT_STATUS_MORE_PROCESSING_REQUIRED then the + * client needs to keep going. If it's an + * error it can be any NT_STATUS error. + * + */ + +#define SMB_REQUEST_TRANSPORT_ENCRYPTION 0x203 /* QFSINFO */ +#define SMB_ENCRYPTION_GSSAPI 0x8000 + +/* The query/set info levels for POSIX ACLs. */ +#define SMB_QUERY_POSIX_ACL 0x204 +#define SMB_SET_POSIX_ACL 0x204 + +/* Current on the wire ACL version. */ +#define SMB_POSIX_ACL_VERSION 1 + +/* ACE entry type. */ +#define SMB_POSIX_ACL_USER_OBJ 0x01 +#define SMB_POSIX_ACL_USER 0x02 +#define SMB_POSIX_ACL_GROUP_OBJ 0x04 +#define SMB_POSIX_ACL_GROUP 0x08 +#define SMB_POSIX_ACL_MASK 0x10 +#define SMB_POSIX_ACL_OTHER 0x20 + +/* perm_t types. */ +#define SMB_POSIX_ACL_READ 0x04 +#define SMB_POSIX_ACL_WRITE 0x02 +#define SMB_POSIX_ACL_EXECUTE 0x01 + +#define SMB_POSIX_ACL_HEADER_SIZE 6 +#define SMB_POSIX_ACL_ENTRY_SIZE 10 + +#define SMB_POSIX_IGNORE_ACE_ENTRIES 0xFFFF + +/* Definition of data block of SMB_SET_POSIX_LOCK */ +/* + [2 bytes] lock_type - 0 = Read, 1 = Write, 2 = Unlock + [2 bytes] lock_flags - 1 = Wait (only valid for setlock) + [4 bytes] pid = locking context. + [8 bytes] start = unsigned 64 bits. + [8 bytes] length = unsigned 64 bits. +*/ + +#define POSIX_LOCK_TYPE_OFFSET 0 +#define POSIX_LOCK_FLAGS_OFFSET 2 +#define POSIX_LOCK_PID_OFFSET 4 +#define POSIX_LOCK_START_OFFSET 8 +#define POSIX_LOCK_LEN_OFFSET 16 +#define POSIX_LOCK_DATA_SIZE 24 + +#define POSIX_LOCK_FLAG_NOWAIT 0 +#define POSIX_LOCK_FLAG_WAIT 1 + +#define POSIX_LOCK_TYPE_READ 0 +#define POSIX_LOCK_TYPE_WRITE 1 +#define POSIX_LOCK_TYPE_UNLOCK 2 + +/* SMB_POSIX_PATH_OPEN "open_mode" definitions. */ +#define SMB_O_RDONLY 0x1 +#define SMB_O_WRONLY 0x2 +#define SMB_O_RDWR 0x4 + +#define SMB_ACCMODE 0x7 + +#define SMB_O_CREAT 0x10 +#define SMB_O_EXCL 0x20 +#define SMB_O_TRUNC 0x40 +#define SMB_O_APPEND 0x80 +#define SMB_O_SYNC 0x100 +#define SMB_O_DIRECTORY 0x200 +#define SMB_O_NOFOLLOW 0x400 +#define SMB_O_DIRECT 0x800 + +/* Definition of request data block for SMB_POSIX_PATH_OPEN */ +/* + [4 bytes] flags (as smb_ntcreate_Flags). + [4 bytes] open_mode - SMB_O_xxx flags above. + [8 bytes] mode_t (permissions) - same encoding as "Standard UNIX permissions" above in SMB_SET_FILE_UNIX_BASIC. + [2 bytes] ret_info_level - optimization. Info level to be returned. +*/ + +/* Definition of reply data block for SMB_POSIX_PATH_OPEN */ + +#define SMB_NO_INFO_LEVEL_RETURNED 0xFFFF + +/* + [2 bytes] - flags field. Identical to flags reply for oplock response field in SMBNTCreateX) + [2 bytes] - FID returned. + [4 bytes] - CreateAction (same as in NTCreateX response). + [2 bytes] - reply info level - as requested or 0xFFFF if not available. + [2 bytes] - padding (must be zero) + [n bytes] - info level reply - if available. +*/ + +/* Definition of request data block for SMB_POSIX_UNLINK */ +/* + [2 bytes] flags (defined below). +*/ + +#define SMB_POSIX_UNLINK_FILE_TARGET 0 +#define SMB_POSIX_UNLINK_DIRECTORY_TARGET 1 + +#endif /* __SMB_UNIX_EXT_H__ */ diff --git a/libcli/smb/smb_util.h b/libcli/smb/smb_util.h new file mode 100644 index 0000000..f2cc0fb --- /dev/null +++ b/libcli/smb/smb_util.h @@ -0,0 +1,57 @@ +/* + Unix SMB/CIFS implementation. + client file operations + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Jeremy Allison 2001-2002 + Copyright (C) James Myers 2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "smb_constants.h" +#include <talloc.h> +#include "libcli/util/ntstatus.h" + +#ifndef _SMB_UTIL_H +#define _SMB_UTIL_H + +const char *smb_protocol_types_string(enum protocol_types protocol); +char *attrib_string(TALLOC_CTX *mem_ctx, uint32_t attrib); +uint32_t unix_perms_to_wire(mode_t perms); +mode_t wire_perms_to_unix(uint32_t perms); +mode_t unix_filetype_from_wire(uint32_t wire_type); + +bool smb_buffer_oob(uint32_t bufsize, uint32_t offset, uint32_t length); + +uint8_t *smb_bytes_push_str(uint8_t *buf, bool ucs2, + const char *str, size_t str_len, + size_t *pconverted_size); +uint8_t *smb_bytes_push_bytes(uint8_t *buf, uint8_t prefix, + const uint8_t *bytes, size_t num_bytes); +uint8_t *trans2_bytes_push_str(uint8_t *buf, bool ucs2, + const char *str, size_t str_len, + size_t *pconverted_size); +uint8_t *trans2_bytes_push_bytes(uint8_t *buf, + const uint8_t *bytes, size_t num_bytes); +NTSTATUS smb_bytes_pull_str(TALLOC_CTX *mem_ctx, char **_str, bool ucs2, + const uint8_t *buf, size_t buf_len, + const uint8_t *position, + size_t *_consumed); + +enum smb_signing_setting smb_signing_setting_translate(const char *str); +enum smb_encryption_setting smb_encryption_setting_translate(const char *str); + +#endif /* _SMB_UTIL_H */ diff --git a/libcli/smb/test_smb1cli_session.c b/libcli/smb/test_smb1cli_session.c new file mode 100644 index 0000000..6a526c9 --- /dev/null +++ b/libcli/smb/test_smb1cli_session.c @@ -0,0 +1,216 @@ +#include <stdarg.h> +#include <stddef.h> +#include <stdint.h> +#include <setjmp.h> +#include <cmocka.h> + +#include "replace.h" +#include <talloc.h> +#include "libcli/util/ntstatus.h" +#include "smb_constants.h" +#include "smb_util.h" + +static const uint8_t smb1_session_setup_bytes[] = { + 0xA1, 0x82, 0x01, 0x02, 0x30, 0x81, 0xFF, 0xA0, + 0x03, 0x0A, 0x01, 0x01, 0xA1, 0x0C, 0x06, 0x0A, + 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, + 0x02, 0x0A, 0xA2, 0x81, 0xE9, 0x04, 0x81, 0xE6, + 0x4E, 0x54, 0x4C, 0x4D, 0x53, 0x53, 0x50, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x16, 0x00, + 0x38, 0x00, 0x00, 0x00, 0x15, 0x82, 0x89, 0x62, + 0xF6, 0x65, 0xAB, 0x23, 0x47, 0xBC, 0x4D, 0x21, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x98, 0x00, 0x98, 0x00, 0x4E, 0x00, 0x00, 0x00, + 0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, + 0x53, 0x00, 0x41, 0x00, 0x4D, 0x00, 0x42, 0x00, + 0x41, 0x00, 0x44, 0x00, 0x4F, 0x00, 0x4D, 0x00, + 0x41, 0x00, 0x49, 0x00, 0x4E, 0x00, 0x02, 0x00, + 0x16, 0x00, 0x53, 0x00, 0x41, 0x00, 0x4D, 0x00, + 0x42, 0x00, 0x41, 0x00, 0x44, 0x00, 0x4F, 0x00, + 0x4D, 0x00, 0x41, 0x00, 0x49, 0x00, 0x4E, 0x00, + 0x01, 0x00, 0x0E, 0x00, 0x4C, 0x00, 0x4F, 0x00, + 0x43, 0x00, 0x41, 0x00, 0x4C, 0x00, 0x44, 0x00, + 0x43, 0x00, 0x04, 0x00, 0x22, 0x00, 0x73, 0x00, + 0x61, 0x00, 0x6D, 0x00, 0x62, 0x00, 0x61, 0x00, + 0x2E, 0x00, 0x65, 0x00, 0x78, 0x00, 0x61, 0x00, + 0x6D, 0x00, 0x70, 0x00, 0x6C, 0x00, 0x65, 0x00, + 0x2E, 0x00, 0x63, 0x00, 0x6F, 0x00, 0x6D, 0x00, + 0x03, 0x00, 0x32, 0x00, 0x6C, 0x00, 0x6F, 0x00, + 0x63, 0x00, 0x61, 0x00, 0x6C, 0x00, 0x64, 0x00, + 0x63, 0x00, 0x2E, 0x00, 0x73, 0x00, 0x61, 0x00, + 0x6D, 0x00, 0x62, 0x00, 0x61, 0x00, 0x2E, 0x00, + 0x65, 0x00, 0x78, 0x00, 0x61, 0x00, 0x6D, 0x00, + 0x70, 0x00, 0x6C, 0x00, 0x65, 0x00, 0x2E, 0x00, + 0x63, 0x00, 0x6F, 0x00, 0x6D, 0x00, 0x07, 0x00, + 0x08, 0x00, 0x0C, 0x40, 0xA3, 0xC3, 0x5B, 0xE0, + 0xD2, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, + 0x00, 0x6E, 0x00, 0x69, 0x00, 0x78, 0x00, 0x00, + 0x00, 0x53, 0x00, 0x61, 0x00, 0x6D, 0x00, 0x62, + 0x00, 0x61, 0x00, 0x20, 0x00, 0x34, 0x00, 0x2E, + 0x00, 0x37, 0x00, 0x2E, 0x00, 0x30, 0x00, 0x70, + 0x00, 0x72, 0x00, 0x65, 0x00, 0x31, 0x00, 0x2D, + 0x00, 0x44, 0x00, 0x45, 0x00, 0x56, 0x00, 0x45, + 0x00, 0x4C, 0x00, 0x4F, 0x00, 0x50, 0x00, 0x45, + 0x00, 0x52, 0x00, 0x42, 0x00, 0x55, 0x00, 0x49, + 0x00, 0x4C, 0x00, 0x44, 0x00, 0x00, 0x00, 0x53, + 0x00, 0x41, 0x00, 0x4D, 0x00, 0x42, 0x00, 0x41, + 0x00, 0x44, 0x00, 0x4F, 0x00, 0x4D, 0x00, 0x41, + 0x00, 0x49, 0x00, 0x4E, 0x00, 0x00, 0x00 +}; + +static void test_smb_bytes_pull_str(void **state) +{ + NTSTATUS status; + const uint8_t *bytes = smb1_session_setup_bytes; + const size_t num_bytes = sizeof(smb1_session_setup_bytes); + const uint8_t *p = NULL; + size_t ret = 0; + size_t out_security_blob_length = 262; + bool use_unicode = true; + char *str = NULL; + + p = bytes; + p += out_security_blob_length; + + status = smb_bytes_pull_str(NULL, &str, use_unicode, + bytes, num_bytes, + p, &ret); + assert_true(NT_STATUS_IS_OK(status)); + assert_string_equal(str, "Unix"); + assert_int_equal(ret, 0x0b); + TALLOC_FREE(str); + + p += ret; + status = smb_bytes_pull_str(NULL, &str, use_unicode, + bytes, num_bytes, + p, &ret); + assert_true(NT_STATUS_IS_OK(status)); + assert_string_equal(str, "Samba 4.7.0pre1-DEVELOPERBUILD"); + assert_int_equal(ret, 0x3e); + TALLOC_FREE(str); + + p += ret; + status = smb_bytes_pull_str(NULL, &str, use_unicode, + bytes, num_bytes, + p, &ret); + assert_true(NT_STATUS_IS_OK(status)); + assert_string_equal(str, "SAMBADOMAIN"); + assert_int_equal(ret, 0x18); + TALLOC_FREE(str); + + p += ret; + status = smb_bytes_pull_str(NULL, &str, use_unicode, + bytes, num_bytes, + p, &ret); + assert_true(NT_STATUS_IS_OK(status)); + assert_string_equal(str, ""); + assert_int_equal(ret, 0x00); + TALLOC_FREE(str); +} + +static void test_smb_bytes_pull_str_no_unicode(void **state) +{ + NTSTATUS status; + const uint8_t *bytes = smb1_session_setup_bytes; + const size_t num_bytes = sizeof(smb1_session_setup_bytes); + const uint8_t *p = NULL; + size_t ret = 0; + size_t out_security_blob_length = 262; + bool use_unicode = false; + char *str = NULL; + + p = bytes; + p += out_security_blob_length; + + status = smb_bytes_pull_str(NULL, &str, use_unicode, + bytes, num_bytes, + p, &ret); + assert_true(NT_STATUS_IS_OK(status)); + assert_string_equal(str, ""); + assert_int_equal(ret, 0x01); + TALLOC_FREE(str); +} + +static void test_smb_bytes_pull_str_wrong_offset(void **state) +{ + NTSTATUS status; + const uint8_t *bytes = smb1_session_setup_bytes; + const size_t num_bytes = sizeof(smb1_session_setup_bytes); + const uint8_t *p = NULL; + size_t ret = 0; + size_t out_security_blob_length = 261; + bool use_unicode = true; + char *str = NULL; + + bytes += 1; + p = bytes; + p += out_security_blob_length; + + status = smb_bytes_pull_str(NULL, &str, use_unicode, + bytes, num_bytes, + p, &ret); + assert_true(NT_STATUS_IS_OK(status)); + + assert_string_equal(str, "\xE5\x94\x80\xE6\xB8\x80\xE6\xA4\x80\xE7\xA0\x80"); + assert_int_equal(ret, 0x0a); + TALLOC_FREE(str); +} + +static void test_smb_bytes_pull_str_invalid_offset(void **state) +{ + NTSTATUS status; + const uint8_t *bytes = smb1_session_setup_bytes; + const size_t num_bytes = sizeof(smb1_session_setup_bytes); + const uint8_t *p = NULL; + size_t ret = 0; + bool use_unicode = true; + char *str = NULL; + intptr_t bytes_address = (intptr_t)bytes; + + /* Warning: array subscript is below array bounds */ + p = (const uint8_t *)(bytes_address - 1); + status = smb_bytes_pull_str(NULL, &str, use_unicode, + bytes, num_bytes, + p, &ret); + assert_int_equal(NT_STATUS_V(status), + NT_STATUS_V(NT_STATUS_INTERNAL_ERROR)); + + p = bytes + num_bytes; + status = smb_bytes_pull_str(NULL, &str, use_unicode, + bytes, num_bytes, + p, &ret); + assert_true(NT_STATUS_IS_OK(status)); + assert_string_equal(str, ""); + assert_int_equal(ret, 0x00); + TALLOC_FREE(str); + + p = bytes + num_bytes - 1; + status = smb_bytes_pull_str(NULL, &str, use_unicode, + bytes, num_bytes, + p, &ret); + assert_true(NT_STATUS_IS_OK(status)); + assert_string_equal(str, ""); + assert_int_equal(ret, 0x01); + TALLOC_FREE(str); + + /* Warning: array subscript is above array bounds */ + p = (const uint8_t *)(bytes_address + num_bytes + 1); + status = smb_bytes_pull_str(NULL, &str, use_unicode, + bytes, num_bytes, + p, &ret); + assert_int_equal(NT_STATUS_V(status), + NT_STATUS_V(NT_STATUS_BUFFER_TOO_SMALL)); +} + +int main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_smb_bytes_pull_str), + cmocka_unit_test(test_smb_bytes_pull_str_no_unicode), + cmocka_unit_test(test_smb_bytes_pull_str_wrong_offset), + cmocka_unit_test(test_smb_bytes_pull_str_invalid_offset), + }; + + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/libcli/smb/test_util_translate.c b/libcli/smb/test_util_translate.c new file mode 100644 index 0000000..b300af5 --- /dev/null +++ b/libcli/smb/test_util_translate.c @@ -0,0 +1,83 @@ +/* + * Unix SMB/CIFS implementation. + * + * Copyright (C) 2020 Andreas Schneider <asn@samba.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdarg.h> +#include <stddef.h> +#include <stdint.h> +#include <setjmp.h> +#include <cmocka.h> + +#include "lib/replace/replace.h" +#include <talloc.h> + +#include "libcli/smb/util.c" + +static void test_smb_signing_setting_translate(void **state) +{ + enum smb_signing_setting signing_state; + + signing_state = smb_signing_setting_translate("wurst"); + assert_int_equal(signing_state, SMB_SIGNING_REQUIRED); + + signing_state = smb_signing_setting_translate("off"); + assert_int_equal(signing_state, SMB_SIGNING_OFF); + + signing_state = smb_signing_setting_translate("if_required"); + assert_int_equal(signing_state, SMB_SIGNING_IF_REQUIRED); + + signing_state = smb_signing_setting_translate("mandatory"); + assert_int_equal(signing_state, SMB_SIGNING_REQUIRED); + +} + +static void test_smb_encryption_setting_translate(void **state) +{ + enum smb_encryption_setting encryption_state; + + encryption_state = smb_encryption_setting_translate("wurst"); + assert_int_equal(encryption_state, SMB_ENCRYPTION_REQUIRED); + + encryption_state = smb_encryption_setting_translate("off"); + assert_int_equal(encryption_state, SMB_ENCRYPTION_OFF); + + encryption_state = smb_encryption_setting_translate("if_required"); + assert_int_equal(encryption_state, SMB_ENCRYPTION_IF_REQUIRED); + + encryption_state = smb_encryption_setting_translate("mandatory"); + assert_int_equal(encryption_state, SMB_ENCRYPTION_REQUIRED); + +} + +int main(int argc, char *argv[]) +{ + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_smb_signing_setting_translate), + cmocka_unit_test(test_smb_encryption_setting_translate), + }; + + if (argc == 2) { + cmocka_set_test_filter(argv[1]); + } + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + + rc = cmocka_run_group_tests(tests, NULL, NULL); + + return rc; +} 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, +}; diff --git a/libcli/smb/tstream_smbXcli_np.h b/libcli/smb/tstream_smbXcli_np.h new file mode 100644 index 0000000..e8c5c39 --- /dev/null +++ b/libcli/smb/tstream_smbXcli_np.h @@ -0,0 +1,72 @@ +/* + 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/>. +*/ + +#ifndef _CLI_NP_TSTREAM_H_ +#define _CLI_NP_TSTREAM_H_ + +struct tevent_context; +struct tevent_req; +struct tstream_context; +struct smbXcli_conn; +struct smbXcli_session; +struct smbXcli_tcon; + +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); +NTSTATUS _tstream_smbXcli_np_open_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct tstream_context **_stream, + const char *location); +#define tstream_smbXcli_np_open_recv(req, mem_ctx, stream) \ + _tstream_smbXcli_np_open_recv(req, mem_ctx, stream, __location__) + +bool tstream_is_smbXcli_np(struct tstream_context *stream); + +NTSTATUS tstream_smbXcli_np_use_trans(struct tstream_context *stream); + +unsigned int tstream_smbXcli_np_set_timeout(struct tstream_context *stream, + unsigned int timeout); + +/* + * Windows uses 4280 (the max xmit/recv size negotiated on DCERPC). + * This is fits into the max_xmit negotiated at the SMB layer. + * + * On the sending side they may use SMBtranss if the request does not + * fit into a single SMBtrans call. + * + * Windows uses 1024 as max data size of a SMBtrans request and then + * possibly reads the rest of the DCERPC fragment (up to 3256 bytes) + * via a SMBreadX. + * + * For now we just ask for the full 4280 bytes (max data size) in the SMBtrans + * request to get the whole fragment at once (like samba 3.5.x and below did. + * + * It is important that we use do SMBwriteX with the size of a full fragment, + * otherwise we may get NT_STATUS_PIPE_BUSY on the SMBtrans request + * from NT4 servers. (See bug #8195) + */ +#define TSTREAM_SMBXCLI_NP_MAX_BUF_SIZE 4280 + +#endif /* _CLI_NP_TSTREAM_H_ */ diff --git a/libcli/smb/util.c b/libcli/smb/util.c new file mode 100644 index 0000000..e1c0f12 --- /dev/null +++ b/libcli/smb/util.c @@ -0,0 +1,716 @@ +/* + Unix SMB/CIFS implementation. + client file operations + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Jeremy Allison 2001-2002 + Copyright (C) James Myers 2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libcli/smb/smb_common.h" +#include "system/filesys.h" +#include "lib/param/loadparm.h" +#include "lib/param/param.h" +#include "libcli/smb/smb2_negotiate_context.h" + +const char *smb_protocol_types_string(enum protocol_types protocol) +{ + switch (protocol) { + case PROTOCOL_DEFAULT: + return "DEFAULT"; + case PROTOCOL_NONE: + return "NONE"; + case PROTOCOL_CORE: + return "CORE"; + case PROTOCOL_COREPLUS: + return "COREPLUS"; + case PROTOCOL_LANMAN1: + return "LANMAN1"; + case PROTOCOL_LANMAN2: + return "LANMAN2"; + case PROTOCOL_NT1: + return "NT1"; + case PROTOCOL_SMB2_02: + return "SMB2_02"; + case PROTOCOL_SMB2_10: + return "SMB2_10"; + case PROTOCOL_SMB3_00: + return "SMB3_00"; + case PROTOCOL_SMB3_02: + return "SMB3_02"; + case PROTOCOL_SMB3_11: + return "SMB3_11"; + } + + return "Invalid protocol_types value"; +} + +/** + Return a string representing a CIFS attribute for a file. +**/ +char *attrib_string(TALLOC_CTX *mem_ctx, uint32_t attrib) +{ + size_t i, len; + const struct { + char c; + uint16_t attr; + } attr_strs[] = { + {'V', FILE_ATTRIBUTE_VOLUME}, + {'D', FILE_ATTRIBUTE_DIRECTORY}, + {'A', FILE_ATTRIBUTE_ARCHIVE}, + {'H', FILE_ATTRIBUTE_HIDDEN}, + {'S', FILE_ATTRIBUTE_SYSTEM}, + {'N', FILE_ATTRIBUTE_NORMAL}, + {'R', FILE_ATTRIBUTE_READONLY}, + {'d', FILE_ATTRIBUTE_DEVICE}, + {'t', FILE_ATTRIBUTE_TEMPORARY}, + {'s', FILE_ATTRIBUTE_SPARSE}, + {'r', FILE_ATTRIBUTE_REPARSE_POINT}, + {'c', FILE_ATTRIBUTE_COMPRESSED}, + {'o', FILE_ATTRIBUTE_OFFLINE}, + {'n', FILE_ATTRIBUTE_NONINDEXED}, + {'e', FILE_ATTRIBUTE_ENCRYPTED} + }; + char *ret; + + ret = talloc_array(mem_ctx, char, ARRAY_SIZE(attr_strs)+1); + if (!ret) { + return NULL; + } + + for (len=i=0; i<ARRAY_SIZE(attr_strs); i++) { + if (attrib & attr_strs[i].attr) { + ret[len++] = attr_strs[i].c; + } + } + + ret[len] = 0; + + talloc_set_name_const(ret, ret); + + return ret; +} + +/**************************************************************************** + Map standard UNIX permissions onto wire representations. +****************************************************************************/ + +uint32_t unix_perms_to_wire(mode_t perms) +{ + unsigned int ret = 0; + + ret |= ((perms & S_IXOTH) ? UNIX_X_OTH : 0); + ret |= ((perms & S_IWOTH) ? UNIX_W_OTH : 0); + ret |= ((perms & S_IROTH) ? UNIX_R_OTH : 0); + ret |= ((perms & S_IXGRP) ? UNIX_X_GRP : 0); + ret |= ((perms & S_IWGRP) ? UNIX_W_GRP : 0); + ret |= ((perms & S_IRGRP) ? UNIX_R_GRP : 0); + ret |= ((perms & S_IXUSR) ? UNIX_X_USR : 0); + ret |= ((perms & S_IWUSR) ? UNIX_W_USR : 0); + ret |= ((perms & S_IRUSR) ? UNIX_R_USR : 0); +#ifdef S_ISVTX + ret |= ((perms & S_ISVTX) ? UNIX_STICKY : 0); +#endif +#ifdef S_ISGID + ret |= ((perms & S_ISGID) ? UNIX_SET_GID : 0); +#endif +#ifdef S_ISUID + ret |= ((perms & S_ISUID) ? UNIX_SET_UID : 0); +#endif + return ret; +} + +/**************************************************************************** + Map wire permissions to standard UNIX. +****************************************************************************/ + +mode_t wire_perms_to_unix(uint32_t perms) +{ + mode_t ret = (mode_t)0; + + ret |= ((perms & UNIX_X_OTH) ? S_IXOTH : 0); + ret |= ((perms & UNIX_W_OTH) ? S_IWOTH : 0); + ret |= ((perms & UNIX_R_OTH) ? S_IROTH : 0); + ret |= ((perms & UNIX_X_GRP) ? S_IXGRP : 0); + ret |= ((perms & UNIX_W_GRP) ? S_IWGRP : 0); + ret |= ((perms & UNIX_R_GRP) ? S_IRGRP : 0); + ret |= ((perms & UNIX_X_USR) ? S_IXUSR : 0); + ret |= ((perms & UNIX_W_USR) ? S_IWUSR : 0); + ret |= ((perms & UNIX_R_USR) ? S_IRUSR : 0); +#ifdef S_ISVTX + ret |= ((perms & UNIX_STICKY) ? S_ISVTX : 0); +#endif +#ifdef S_ISGID + ret |= ((perms & UNIX_SET_GID) ? S_ISGID : 0); +#endif +#ifdef S_ISUID + ret |= ((perms & UNIX_SET_UID) ? S_ISUID : 0); +#endif + return ret; +} + +/**************************************************************************** + Return the file type from the wire filetype for UNIX extensions. +****************************************************************************/ + +mode_t unix_filetype_from_wire(uint32_t wire_type) +{ + switch (wire_type) { + case UNIX_TYPE_FILE: + return S_IFREG; + case UNIX_TYPE_DIR: + return S_IFDIR; +#ifdef S_IFLNK + case UNIX_TYPE_SYMLINK: + return S_IFLNK; +#endif +#ifdef S_IFCHR + case UNIX_TYPE_CHARDEV: + return S_IFCHR; +#endif +#ifdef S_IFBLK + case UNIX_TYPE_BLKDEV: + return S_IFBLK; +#endif +#ifdef S_IFIFO + case UNIX_TYPE_FIFO: + return S_IFIFO; +#endif +#ifdef S_IFSOCK + case UNIX_TYPE_SOCKET: + return S_IFSOCK; +#endif + default: + return (mode_t)0; + } +} + +bool smb_buffer_oob(uint32_t bufsize, uint32_t offset, uint32_t length) +{ + if ((offset + length < offset) || (offset + length < length)) { + /* wrap */ + return true; + } + if ((offset > bufsize) || (offset + length > bufsize)) { + /* overflow */ + return true; + } + return false; +} + +/*********************************************************** + Common function for pushing stings, used by smb_bytes_push_str() + and trans_bytes_push_str(). Only difference is the align_odd + parameter setting. +***********************************************************/ + +static uint8_t *internal_bytes_push_str(uint8_t *buf, bool ucs2, + const char *str, size_t str_len, + bool align_odd, + size_t *pconverted_size) +{ + TALLOC_CTX *frame = talloc_stackframe(); + size_t buflen; + char *converted; + size_t converted_size; + + /* + * This check prevents us from + * (re)alloc buf on a NULL TALLOC_CTX. + */ + if (buf == NULL) { + TALLOC_FREE(frame); + return NULL; + } + + buflen = talloc_get_size(buf); + + if (ucs2 && + ((align_odd && (buflen % 2 == 0)) || + (!align_odd && (buflen % 2 == 1)))) { + /* + * We're pushing into an SMB buffer, align odd + */ + buf = talloc_realloc(NULL, buf, uint8_t, buflen + 1); + if (buf == NULL) { + TALLOC_FREE(frame); + return NULL; + } + buf[buflen] = '\0'; + buflen += 1; + } + + if (!convert_string_talloc(frame, CH_UNIX, + ucs2 ? CH_UTF16LE : CH_DOS, + str, str_len, &converted, + &converted_size)) { + TALLOC_FREE(frame); + return NULL; + } + + buf = talloc_realloc(NULL, buf, uint8_t, + buflen + converted_size); + if (buf == NULL) { + TALLOC_FREE(frame); + return NULL; + } + + memcpy(buf + buflen, converted, converted_size); + + TALLOC_FREE(converted); + + if (pconverted_size) { + *pconverted_size = converted_size; + } + + TALLOC_FREE(frame); + return buf; +} + +/*********************************************************** + Push a string into an SMB buffer, with odd byte alignment + if it's a UCS2 string. +***********************************************************/ + +uint8_t *smb_bytes_push_str(uint8_t *buf, bool ucs2, + const char *str, size_t str_len, + size_t *pconverted_size) +{ + return internal_bytes_push_str(buf, ucs2, str, str_len, + true, pconverted_size); +} + +uint8_t *smb_bytes_push_bytes(uint8_t *buf, uint8_t prefix, + const uint8_t *bytes, size_t num_bytes) +{ + size_t buflen; + + /* + * This check prevents us from + * (re)alloc buf on a NULL TALLOC_CTX. + */ + if (buf == NULL) { + return NULL; + } + buflen = talloc_get_size(buf); + + buf = talloc_realloc(NULL, buf, uint8_t, + buflen + 1 + num_bytes); + if (buf == NULL) { + return NULL; + } + buf[buflen] = prefix; + memcpy(&buf[buflen+1], bytes, num_bytes); + return buf; +} + +/*********************************************************** + Same as smb_bytes_push_str(), but without the odd byte + align for ucs2 (we're pushing into a param or data block). + static for now, although this will probably change when + other modules use async trans calls. +***********************************************************/ + +uint8_t *trans2_bytes_push_str(uint8_t *buf, bool ucs2, + const char *str, size_t str_len, + size_t *pconverted_size) +{ + return internal_bytes_push_str(buf, ucs2, str, str_len, + false, pconverted_size); +} + +uint8_t *trans2_bytes_push_bytes(uint8_t *buf, + const uint8_t *bytes, size_t num_bytes) +{ + size_t buflen; + + if (buf == NULL) { + return NULL; + } + buflen = talloc_get_size(buf); + + buf = talloc_realloc(NULL, buf, uint8_t, + buflen + num_bytes); + if (buf == NULL) { + return NULL; + } + memcpy(&buf[buflen], bytes, num_bytes); + return buf; +} + +static NTSTATUS internal_bytes_pull_str(TALLOC_CTX *mem_ctx, char **_str, + bool ucs2, bool align_odd, + const uint8_t *buf, size_t buf_len, + const uint8_t *position, + size_t *p_consumed) +{ + size_t pad = 0; + size_t offset; + char *str = NULL; + size_t str_len = 0; + bool ok; + + *_str = NULL; + if (p_consumed != NULL) { + *p_consumed = 0; + } + + if (position < buf) { + return NT_STATUS_INTERNAL_ERROR; + } + + offset = PTR_DIFF(position, buf); + if (offset > buf_len) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + + if (ucs2 && + ((align_odd && (offset % 2 == 0)) || + (!align_odd && (offset % 2 == 1)))) { + pad += 1; + offset += 1; + } + + if (offset > buf_len) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + + buf_len -= offset; + buf += offset; + + if (ucs2) { + buf_len = utf16_len_n(buf, buf_len); + } else { + size_t tmp = strnlen((const char *)buf, buf_len); + if (tmp < buf_len) { + tmp += 1; + } + buf_len = tmp; + } + + ok = convert_string_talloc(mem_ctx, + ucs2 ? CH_UTF16LE : CH_DOS, + CH_UNIX, + buf, buf_len, + &str, &str_len); + if (!ok) { + return map_nt_error_from_unix_common(errno); + } + + if (p_consumed != NULL) { + *p_consumed = buf_len + pad; + } + *_str = str; + return NT_STATUS_OK; +} + +NTSTATUS smb_bytes_pull_str(TALLOC_CTX *mem_ctx, char **_str, bool ucs2, + const uint8_t *buf, size_t buf_len, + const uint8_t *position, + size_t *_consumed) +{ + return internal_bytes_pull_str(mem_ctx, _str, ucs2, true, + buf, buf_len, position, _consumed); +} + +/** + * @brief Translate SMB signing settings as string to an enum. + * + * @param[in] str The string to translate. + * + * @return A corresponding enum @smb_signing_setting tranlated from the string. + */ +enum smb_signing_setting smb_signing_setting_translate(const char *str) +{ + enum smb_signing_setting signing_state = SMB_SIGNING_REQUIRED; + int32_t val = lpcfg_parse_enum_vals("client signing", str); + + if (val != INT32_MIN) { + signing_state = val; + } + + return signing_state; +} + +/** + * @brief Translate SMB encryption settings as string to an enum. + * + * @param[in] str The string to translate. + * + * @return A corresponding enum @smb_encryption_setting tranlated from the + * string. + */ +enum smb_encryption_setting smb_encryption_setting_translate(const char *str) +{ + enum smb_encryption_setting encryption_state = SMB_ENCRYPTION_REQUIRED; + int32_t val = lpcfg_parse_enum_vals("client smb encrypt", str); + + if (val != INT32_MIN) { + encryption_state = val; + } + + return encryption_state; +} + +static const struct enum_list enum_smb3_signing_algorithms[] = { + {SMB2_SIGNING_AES128_GMAC, "AES-128-GMAC"}, + {SMB2_SIGNING_AES128_CMAC, "AES-128-CMAC"}, + {SMB2_SIGNING_HMAC_SHA256, "HMAC-SHA256"}, + {-1, NULL} +}; + +const char *smb3_signing_algorithm_name(uint16_t algo) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(enum_smb3_signing_algorithms); i++) { + if (enum_smb3_signing_algorithms[i].value != algo) { + continue; + } + + return enum_smb3_signing_algorithms[i].name; + } + + return NULL; +} + +static const struct enum_list enum_smb3_encryption_algorithms[] = { + {SMB2_ENCRYPTION_AES128_GCM, "AES-128-GCM"}, + {SMB2_ENCRYPTION_AES128_CCM, "AES-128-CCM"}, + {SMB2_ENCRYPTION_AES256_GCM, "AES-256-GCM"}, + {SMB2_ENCRYPTION_AES256_CCM, "AES-256-CCM"}, + {-1, NULL} +}; + +const char *smb3_encryption_algorithm_name(uint16_t algo) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(enum_smb3_encryption_algorithms); i++) { + if (enum_smb3_encryption_algorithms[i].value != algo) { + continue; + } + + return enum_smb3_encryption_algorithms[i].name; + } + + return NULL; +} + +static int32_t parse_enum_val(const struct enum_list *e, + const char *param_name, + const char *param_value) +{ + struct parm_struct parm = { + .label = param_name, + .type = P_LIST, + .p_class = P_GLOBAL, + .enum_list = e, + }; + int32_t ret = INT32_MIN; + bool ok; + + ok = lp_set_enum_parm(&parm, param_value, &ret); + if (!ok) { + return INT32_MIN; + } + + return ret; +} + +struct smb311_capabilities smb311_capabilities_parse(const char *role, + const char * const *signing_algos, + const char * const *encryption_algos) +{ + struct smb311_capabilities c = { + .signing = { + .num_algos = 0, + }, + .encryption = { + .num_algos = 0, + }, + }; + char sign_param[64] = { 0, }; + char enc_param[64] = { 0, }; + size_t ai; + + snprintf(sign_param, sizeof(sign_param), + "%s smb3 signing algorithms", role); + snprintf(enc_param, sizeof(enc_param), + "%s smb3 encryption algorithms", role); + + for (ai = 0; signing_algos != NULL && signing_algos[ai] != NULL; ai++) { + const char *algoname = signing_algos[ai]; + int32_t v32; + uint16_t algo; + size_t di; + bool ignore = false; + + if (c.signing.num_algos >= SMB3_ENCRYTION_CAPABILITIES_MAX_ALGOS) { + DBG_ERR("WARNING: Ignoring trailing value '%s' for parameter '%s'\n", + algoname, sign_param); + continue; + } + + v32 = parse_enum_val(enum_smb3_signing_algorithms, + sign_param, algoname); + if (v32 == INT32_MAX) { + continue; + } + algo = v32; + + for (di = 0; di < c.signing.num_algos; di++) { + if (algo != c.signing.algos[di]) { + continue; + } + + ignore = true; + break; + } + + if (ignore) { + DBG_ERR("WARNING: Ignoring duplicate value '%s' for parameter '%s'\n", + algoname, sign_param); + continue; + } + + c.signing.algos[c.signing.num_algos] = algo; + c.signing.num_algos += 1; + } + + for (ai = 0; encryption_algos != NULL && encryption_algos[ai] != NULL; ai++) { + const char *algoname = encryption_algos[ai]; + int32_t v32; + uint16_t algo; + size_t di; + bool ignore = false; + + if (c.encryption.num_algos >= SMB3_ENCRYTION_CAPABILITIES_MAX_ALGOS) { + DBG_ERR("WARNING: Ignoring trailing value '%s' for parameter '%s'\n", + algoname, enc_param); + continue; + } + + v32 = parse_enum_val(enum_smb3_encryption_algorithms, + enc_param, algoname); + if (v32 == INT32_MAX) { + continue; + } + algo = v32; + + for (di = 0; di < c.encryption.num_algos; di++) { + if (algo != c.encryption.algos[di]) { + continue; + } + + ignore = true; + break; + } + + if (ignore) { + DBG_ERR("WARNING: Ignoring duplicate value '%s' for parameter '%s'\n", + algoname, enc_param); + continue; + } + + c.encryption.algos[c.encryption.num_algos] = algo; + c.encryption.num_algos += 1; + } + + return c; +} + +NTSTATUS smb311_capabilities_check(const struct smb311_capabilities *c, + const char *debug_prefix, + int debug_lvl, + NTSTATUS error_status, + const char *role, + enum protocol_types protocol, + uint16_t sign_algo, + uint16_t cipher_algo) +{ + const struct smb3_signing_capabilities *sign_algos = + &c->signing; + const struct smb3_encryption_capabilities *ciphers = + &c->encryption; + bool found_signing = false; + bool found_encryption = false; + size_t i; + + for (i = 0; i < sign_algos->num_algos; i++) { + if (sign_algo == sign_algos->algos[i]) { + /* + * We found a match + */ + found_signing = true; + break; + } + } + + for (i = 0; i < ciphers->num_algos; i++) { + if (cipher_algo == SMB2_ENCRYPTION_NONE) { + /* + * encryption not supported, we'll error out later + */ + found_encryption = true; + break; + } + + if (cipher_algo == ciphers->algos[i]) { + /* + * We found a match + */ + found_encryption = true; + break; + } + } + + if (!found_signing) { + /* + * We negotiated a signing algo we don't allow, + * most likely for SMB < 3.1.1 + */ + DEBUG(debug_lvl,("%s: " + "SMB3 signing algorithm[%u][%s] on dialect[%s] " + "not allowed by '%s smb3 signing algorithms' - %s.\n", + debug_prefix, + sign_algo, + smb3_signing_algorithm_name(sign_algo), + smb_protocol_types_string(protocol), + role, + nt_errstr(error_status))); + return error_status; + } + + if (!found_encryption) { + /* + * We negotiated a cipher we don't allow, + * most likely for SMB 3.0 and 3.0.2 + */ + DEBUG(debug_lvl,("%s: " + "SMB3 encryption algorithm[%u][%s] on dialect[%s] " + "not allowed by '%s smb3 encryption algorithms' - %s.\n", + debug_prefix, + cipher_algo, + smb3_encryption_algorithm_name(cipher_algo), + smb_protocol_types_string(protocol), + role, + nt_errstr(error_status))); + return error_status; + } + + return NT_STATUS_OK; +} diff --git a/libcli/smb/wscript b/libcli/smb/wscript new file mode 100644 index 0000000..0c9b38c --- /dev/null +++ b/libcli/smb/wscript @@ -0,0 +1,80 @@ +#!/usr/bin/env python + + +def build(bld): + bld.SAMBA_LIBRARY('smb_transport', + source=''' + read_smb.c + ''', + deps='LIBASYNC_REQ', + public_deps='talloc tevent', + private_library=True, + private_headers=''' + read_smb.h + ''', + ) + + bld.SAMBA_LIBRARY('cli_smb_common', + source=''' + smb_signing.c + smb_seal.c + smb2_negotiate_context.c + smb2_create_blob.c smb2_signing.c + smb2_lease.c + util.c + smbXcli_base.c + smb1cli_trans.c + smb1cli_echo.c + smb1cli_create.c + smb1cli_session.c + smb1cli_close.c + smb1cli_write.c + smb1cli_read.c + smb2cli_session.c + smb2cli_tcon.c + smb2cli_create.c + smb2cli_close.c + smb2cli_read.c + smb2cli_write.c + smb2cli_flush.c + smb2cli_set_info.c + smb2cli_query_info.c + smb2cli_notify.c + smb2cli_query_directory.c + smb2cli_ioctl.c + smb2cli_echo.c + smb2_posix.c + tstream_smbXcli_np.c + reparse_symlink.c + ''', + deps=''' + LIBCRYPTO gnutls NDR_SMB2_LEASE_STRUCT samba-errors gensec krb5samba + smb_transport GNUTLS_HELPERS + ''', + public_deps='talloc samba-util iov_buf', + private_library=True, + private_headers=''' + smb_common.h + smb2_constants.h + smb_constants.h + smb_signing.h + smb_seal.h + smb2_create_blob.h + smb2_signing.h + smb2_lease.h + smb_util.h + smb_unix_ext.h + smb_posix.h + tstream_smbXcli_np.h + ''', + ) + + bld.SAMBA_BINARY('test_smb1cli_session', + source='test_smb1cli_session.c', + deps='cmocka cli_smb_common', + for_selftest=True) + + bld.SAMBA_BINARY('test_util_translate', + source='test_util_translate.c', + deps='cmocka cli_smb_common', + for_selftest=True) diff --git a/libcli/smbreadline/smbreadline.c b/libcli/smbreadline/smbreadline.c new file mode 100644 index 0000000..6929209 --- /dev/null +++ b/libcli/smbreadline/smbreadline.c @@ -0,0 +1,184 @@ +/* + Unix SMB/CIFS implementation. + Samba readline wrapper implementation + Copyright (C) Simo Sorce 2001 + Copyright (C) Andrew Tridgell 2001 + + 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/util/select.h" +#include "system/filesys.h" +#include "system/select.h" +#include "system/readline.h" +#include "libcli/smbreadline/smbreadline.h" + +#undef malloc + +#ifdef HAVE_LIBREADLINE +# ifdef HAVE_READLINE_READLINE_H +# include <readline/readline.h> +# ifdef HAVE_READLINE_HISTORY_H +# include <readline/history.h> +# endif +# else +# ifdef HAVE_READLINE_H +# include <readline.h> +# ifdef HAVE_HISTORY_H +# include <history.h> +# endif +# else +# undef HAVE_LIBREADLINE +# endif +# endif +#endif + +static bool smb_rl_done; + +#ifdef HAVE_LIBREADLINE +/* + * MacOS/X does not have rl_done in readline.h, but + * readline.so has it + */ +extern int rl_done; +#endif + +void smb_readline_done(void) +{ + smb_rl_done = true; +#ifdef HAVE_LIBREADLINE + rl_done = 1; +#endif +} + +/**************************************************************************** + Display the prompt and wait for input. Call callback() regularly +****************************************************************************/ + +static char *smb_readline_replacement(const char *prompt, void (*callback)(void), + char **(completion_fn)(const char *text, int start, int end)) +{ + char *line = NULL; + int fd = fileno(stdin); + char *ret; + + /* Prompt might be NULL in non-interactive mode. */ + if (prompt) { + printf("%s", prompt); + fflush(stdout); + } + + line = (char *)malloc(BUFSIZ); + if (!line) { + return NULL; + } + + while (!smb_rl_done) { + struct pollfd pfd; + + ZERO_STRUCT(pfd); + pfd.fd = fd; + pfd.events = POLLIN|POLLHUP; + + if (sys_poll_intr(&pfd, 1, 5000) == 1) { + ret = fgets(line, BUFSIZ, stdin); + if (ret == 0) { + SAFE_FREE(line); + } + return ret; + } + if (callback) { + callback(); + } + } + SAFE_FREE(line); + return NULL; +} + +/**************************************************************************** + Display the prompt and wait for input. Call callback() regularly. +****************************************************************************/ + +char *smb_readline(const char *prompt, void (*callback)(void), + char **(completion_fn)(const char *text, int start, int end)) +{ + char *ret; + bool interactive; + + interactive = isatty(fileno(stdin)) || getenv("CLI_FORCE_INTERACTIVE"); + if (!interactive) { + return smb_readline_replacement(NULL, callback, completion_fn); + } + +#ifdef HAVE_LIBREADLINE + + /* Aargh! Readline does bizzare things with the terminal width + that mucks up expect(1). Set CLI_NO_READLINE in the environment + to force readline not to be used. */ + + if (getenv("CLI_NO_READLINE")) + return smb_readline_replacement(prompt, callback, completion_fn); + + if (completion_fn) { + /* The callback prototype has changed slightly between + different versions of Readline, so the same function + works in all of them to date, but we get compiler + warnings in some. */ + rl_attempted_completion_function = RL_COMPLETION_CAST completion_fn; + + /* + * We only want sensible characters as the word-break chars + * for the most part. This allows us to tab through a path. + */ + rl_basic_word_break_characters = " \t\n"; + } + +#ifdef HAVE_DECL_RL_EVENT_HOOK + if (callback) + rl_event_hook = (rl_hook_func_t *)callback; +#endif + ret = readline(prompt); + if (ret && *ret) + add_history(ret); + +#else + ret = smb_readline_replacement(prompt, callback, completion_fn); +#endif + + return ret; +} + +/**************************************************************************** + * return line buffer text + ****************************************************************************/ +const char *smb_readline_get_line_buffer(void) +{ +#if defined(HAVE_LIBREADLINE) + return rl_line_buffer; +#else + return NULL; +#endif +} + + +/**************************************************************************** + * set completion append character + ***************************************************************************/ +void smb_readline_ca_char(char c) +{ +#if defined(HAVE_LIBREADLINE) + rl_completion_append_character = c; +#endif +} diff --git a/libcli/smbreadline/smbreadline.h b/libcli/smbreadline/smbreadline.h new file mode 100644 index 0000000..9adc4b3 --- /dev/null +++ b/libcli/smbreadline/smbreadline.h @@ -0,0 +1,30 @@ +/* + Unix SMB/CIFS implementation. + Samba readline wrapper implementation + Copyright (C) Simo Sorce 2001 + Copyright (C) Andrew Tridgell 2001 + + 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 __SMBREADLINE_H__ +#define __SMBREADLINE_H__ + +char *smb_readline(const char *prompt, void (*callback)(void), + char **(completion_fn)(const char *text, int start, int end)); +const char *smb_readline_get_line_buffer(void); +void smb_readline_ca_char(char c); +void smb_readline_done(void); + +#endif /* __SMBREADLINE_H__ */ diff --git a/libcli/smbreadline/wscript_build b/libcli/smbreadline/wscript_build new file mode 100644 index 0000000..17699ea --- /dev/null +++ b/libcli/smbreadline/wscript_build @@ -0,0 +1,8 @@ +#!/usr/bin/env python + + +termlib=bld.env.READLINE_TERMLIB or '' + +bld.SAMBA_SUBSYSTEM('SMBREADLINE', + source='smbreadline.c', + deps=termlib + ' readline talloc') diff --git a/libcli/smbreadline/wscript_configure b/libcli/smbreadline/wscript_configure new file mode 100644 index 0000000..912ff53 --- /dev/null +++ b/libcli/smbreadline/wscript_configure @@ -0,0 +1,85 @@ +#!/usr/bin/env python + + +conf.CHECK_HEADERS('readline.h history.h readline/readline.h readline/history.h') +for termlib in ['ncurses', 'curses', 'termcap', 'terminfo', 'termlib', 'tinfo']: + if conf.CHECK_FUNCS_IN('tgetent', termlib): + conf.env['READLINE_TERMLIB'] = termlib + break + +# +# Check if we need to work around readline/readline.h +# deprecated declarations +# +if conf.CONFIG_SET('HAVE_READLINE_READLINE_H'): + if not conf.CHECK_CODE(''' + #include <readline/readline.h> + int main() {return 0;} + ''', + define='HAVE_WORKING_READLINE_READLINE_WITH_STRICT_PROTO', + cflags=conf.env['WERROR_CFLAGS'] + + ['-Wstrict-prototypes', + '-Werror=strict-prototypes'], + msg='for compiling <readline/readline.h> with strict prototypes', + addmain=False): + conf.CHECK_CODE(''' + #define _FUNCTION_DEF + #include <readline/readline.h> + int main() {return 0;} + ''', + cflags=conf.env['WERROR_CFLAGS'] + + ['-Wstrict-prototypes', + '-Werror=strict-prototypes'], + msg='for workaround to <readline/readline.h> strict prototypes issue', + define='HAVE_READLINE_READLINE_WORKAROUND', + addmain=False) + +conf.CHECK_CODE(''' +#ifdef HAVE_READLINE_READLINE_H +# ifdef HAVE_READLINE_READLINE_WORKAROUND +# define _FUNCTION_DEF +# endif +# include <readline/readline.h> +# ifdef HAVE_READLINE_HISTORY_H +# include <readline/history.h> +# endif +#else +# ifdef HAVE_READLINE_H +# include <readline.h> +# ifdef HAVE_HISTORY_H +# include <history.h> +# endif +# endif +#endif +int main(void) {rl_completion_t f; return 0;} +''', +'HAVE_RL_COMPLETION_FUNC_T', execute=False, addmain=False, +msg='Checking for rl_completion_t') + +conf.CHECK_CODE(''' +#ifdef HAVE_READLINE_READLINE_H +# ifdef HAVE_READLINE_READLINE_WORKAROUND +# define _FUNCTION_DEF +# endif +# include <readline/readline.h> +# ifdef HAVE_READLINE_HISTORY_H +# include <readline/history.h> +# endif +#else +# ifdef HAVE_READLINE_H +# include <readline.h> +# ifdef HAVE_HISTORY_H +# include <history.h> +# endif +# endif +#endif +int main(void) {CPPFunction f; return 0;} +''', +'HAVE_CPPFUNCTION', execute=False, addmain=False, +msg='Checking for CPPFunction') + +if conf.CHECK_FUNCS_IN('rl_completion_matches', 'readline'): + conf.DEFINE('HAVE_NEW_LIBREADLINE', 1) + +if conf.CHECK_FUNCS_IN('history_list', 'readline'): + conf.DEFINE('HAVE_HISTORY_LIST', 1) |