diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
commit | 4f5791ebd03eaec1c7da0865a383175b05102712 (patch) | |
tree | 8ce7b00f7a76baa386372422adebbe64510812d4 /source4/smb_server/smb2 | |
parent | Initial commit. (diff) | |
download | samba-4f5791ebd03eaec1c7da0865a383175b05102712.tar.xz samba-4f5791ebd03eaec1c7da0865a383175b05102712.zip |
Adding upstream version 2:4.17.12+dfsg.upstream/2%4.17.12+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source4/smb_server/smb2')
-rw-r--r-- | source4/smb_server/smb2/fileinfo.c | 377 | ||||
-rw-r--r-- | source4/smb_server/smb2/fileio.c | 547 | ||||
-rw-r--r-- | source4/smb_server/smb2/find.c | 167 | ||||
-rw-r--r-- | source4/smb_server/smb2/keepalive.c | 71 | ||||
-rw-r--r-- | source4/smb_server/smb2/negprot.c | 327 | ||||
-rw-r--r-- | source4/smb_server/smb2/receive.c | 710 | ||||
-rw-r--r-- | source4/smb_server/smb2/sesssetup.c | 326 | ||||
-rw-r--r-- | source4/smb_server/smb2/smb2_server.h | 192 | ||||
-rw-r--r-- | source4/smb_server/smb2/tcon.c | 446 | ||||
-rw-r--r-- | source4/smb_server/smb2/wscript_build | 9 |
10 files changed, 3172 insertions, 0 deletions
diff --git a/source4/smb_server/smb2/fileinfo.c b/source4/smb_server/smb2/fileinfo.c new file mode 100644 index 0000000..a90c41a --- /dev/null +++ b/source4/smb_server/smb2/fileinfo.c @@ -0,0 +1,377 @@ +/* + Unix SMB2 implementation. + + Copyright (C) Stefan Metzmacher 2006 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "smb_server/smb_server.h" +#include "smb_server/smb2/smb2_server.h" +#include "ntvfs/ntvfs.h" +#include "librpc/gen_ndr/ndr_security.h" + +struct smb2srv_getinfo_op { + struct smb2srv_request *req; + struct smb2_getinfo *info; + void *io_ptr; + NTSTATUS (*send_fn)(struct smb2srv_getinfo_op *op); +}; + +static void smb2srv_getinfo_send(struct ntvfs_request *ntvfs) +{ + struct smb2srv_getinfo_op *op; + struct smb2srv_request *req; + + /* + * SMB2 uses NT_STATUS_INVALID_INFO_CLASS + * so we need to translated it here + */ + if (NT_STATUS_EQUAL(NT_STATUS_INVALID_LEVEL, ntvfs->async_states->status)) { + ntvfs->async_states->status = NT_STATUS_INVALID_INFO_CLASS; + } + + SMB2SRV_CHECK_ASYNC_STATUS(op, struct smb2srv_getinfo_op); + + ZERO_STRUCT(op->info->out); + if (op->send_fn) { + SMB2SRV_CHECK(op->send_fn(op)); + } + + if (op->info->in.output_buffer_length < op->info->out.blob.length) { + smb2srv_send_error(req, NT_STATUS_INFO_LENGTH_MISMATCH); + return; + } + + SMB2SRV_CHECK(smb2srv_setup_reply(req, 0x08, true, op->info->out.blob.length)); + + SMB2SRV_CHECK(smb2_push_o16s32_blob(&req->out, 0x02, op->info->out.blob)); + SSVAL(req->out.body, 0x06, 0); + + smb2srv_send_reply(req); +} + +static NTSTATUS smb2srv_getinfo_file_send(struct smb2srv_getinfo_op *op) +{ + union smb_fileinfo *io = talloc_get_type(op->io_ptr, union smb_fileinfo); + NTSTATUS status; + + status = smbsrv_push_passthru_fileinfo(op->req, + &op->info->out.blob, + io->generic.level, io, + STR_UNICODE); + NT_STATUS_NOT_OK_RETURN(status); + + return NT_STATUS_OK; +} + +static NTSTATUS smb2srv_getinfo_file(struct smb2srv_getinfo_op *op, uint8_t smb2_level) +{ + union smb_fileinfo *io; + uint16_t level; + + io = talloc(op, union smb_fileinfo); + NT_STATUS_HAVE_NO_MEMORY(io); + + level = op->info->in.info_type | (op->info->in.info_class << 8); + switch (level) { + case RAW_FILEINFO_SMB2_ALL_EAS: + io->all_eas.level = level; + io->all_eas.in.file.ntvfs = op->info->in.file.ntvfs; + io->all_eas.in.continue_flags = op->info->in.getinfo_flags; + break; + + case RAW_FILEINFO_SMB2_ALL_INFORMATION: + io->all_info2.level = level; + io->all_info2.in.file.ntvfs = op->info->in.file.ntvfs; + break; + + default: + /* the rest directly maps to the passthru levels */ + io->generic.level = smb2_level + 1000; + io->generic.in.file.ntvfs = op->info->in.file.ntvfs; + break; + } + + op->io_ptr = io; + op->send_fn = smb2srv_getinfo_file_send; + + return ntvfs_qfileinfo(op->req->ntvfs, io); +} + +static NTSTATUS smb2srv_getinfo_fs_send(struct smb2srv_getinfo_op *op) +{ + union smb_fsinfo *io = talloc_get_type(op->io_ptr, union smb_fsinfo); + NTSTATUS status; + + status = smbsrv_push_passthru_fsinfo(op->req, + &op->info->out.blob, + io->generic.level, io, + STR_UNICODE); + NT_STATUS_NOT_OK_RETURN(status); + + return NT_STATUS_OK; +} + +static NTSTATUS smb2srv_getinfo_fs(struct smb2srv_getinfo_op *op, uint8_t smb2_level) +{ + union smb_fsinfo *io; + + io = talloc(op, union smb_fsinfo); + NT_STATUS_HAVE_NO_MEMORY(io); + + /* the rest directly maps to the passthru levels */ + io->generic.level = smb2_level + 1000; + + /* TODO: allow qfsinfo only the share root directory handle */ + + op->io_ptr = io; + op->send_fn = smb2srv_getinfo_fs_send; + + return ntvfs_fsinfo(op->req->ntvfs, io); +} + +static NTSTATUS smb2srv_getinfo_security_send(struct smb2srv_getinfo_op *op) +{ + union smb_fileinfo *io = talloc_get_type(op->io_ptr, union smb_fileinfo); + enum ndr_err_code ndr_err; + + ndr_err = ndr_push_struct_blob(&op->info->out.blob, op->req, + io->query_secdesc.out.sd, + (ndr_push_flags_fn_t)ndr_push_security_descriptor); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ndr_map_error2ntstatus(ndr_err); + } + + return NT_STATUS_OK; +} + +static NTSTATUS smb2srv_getinfo_security(struct smb2srv_getinfo_op *op, uint8_t smb2_level) +{ + union smb_fileinfo *io; + + switch (smb2_level) { + case 0x00: + io = talloc(op, union smb_fileinfo); + NT_STATUS_HAVE_NO_MEMORY(io); + + io->query_secdesc.level = RAW_FILEINFO_SEC_DESC; + io->query_secdesc.in.file.ntvfs = op->info->in.file.ntvfs; + io->query_secdesc.in.secinfo_flags = op->info->in.additional_information; + + op->io_ptr = io; + op->send_fn = smb2srv_getinfo_security_send; + + return ntvfs_qfileinfo(op->req->ntvfs, io); + } + + return NT_STATUS_INVALID_PARAMETER; +} + +static NTSTATUS smb2srv_getinfo_backend(struct smb2srv_getinfo_op *op) +{ + switch (op->info->in.info_type) { + case SMB2_0_INFO_FILE: + return smb2srv_getinfo_file(op, op->info->in.info_class); + + case SMB2_0_INFO_FILESYSTEM: + return smb2srv_getinfo_fs(op, op->info->in.info_class); + + case SMB2_0_INFO_SECURITY: + return smb2srv_getinfo_security(op, op->info->in.info_class); + + case SMB2_0_INFO_QUOTA: + return NT_STATUS_NOT_SUPPORTED; + } + + return NT_STATUS_INVALID_PARAMETER; +} + +void smb2srv_getinfo_recv(struct smb2srv_request *req) +{ + struct smb2_getinfo *info; + struct smb2srv_getinfo_op *op; + + SMB2SRV_CHECK_BODY_SIZE(req, 0x28, true); + SMB2SRV_TALLOC_IO_PTR(info, struct smb2_getinfo); + /* this overwrites req->io_ptr !*/ + SMB2SRV_TALLOC_IO_PTR(op, struct smb2srv_getinfo_op); + op->req = req; + op->info = info; + op->io_ptr = NULL; + op->send_fn = NULL; + SMB2SRV_SETUP_NTVFS_REQUEST(smb2srv_getinfo_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + info->in.info_type = CVAL(req->in.body, 0x02); + info->in.info_class = CVAL(req->in.body, 0x03); + info->in.output_buffer_length = IVAL(req->in.body, 0x04); + info->in.reserved = IVAL(req->in.body, 0x0C); + info->in.additional_information = IVAL(req->in.body, 0x10); + info->in.getinfo_flags = IVAL(req->in.body, 0x14); + info->in.file.ntvfs = smb2srv_pull_handle(req, req->in.body, 0x18); + SMB2SRV_CHECK(smb2_pull_o16As32_blob(&req->in, op, + req->in.body+0x08, &info->in.input_buffer)); + + SMB2SRV_CHECK_FILE_HANDLE(info->in.file.ntvfs); + SMB2SRV_CALL_NTVFS_BACKEND(smb2srv_getinfo_backend(op)); +} + +struct smb2srv_setinfo_op { + struct smb2srv_request *req; + struct smb2_setinfo *info; +}; + +static void smb2srv_setinfo_send(struct ntvfs_request *ntvfs) +{ + struct smb2srv_setinfo_op *op; + struct smb2srv_request *req; + + /* + * SMB2 uses NT_STATUS_INVALID_INFO_CLASS + * so we need to translated it here + */ + if (NT_STATUS_EQUAL(NT_STATUS_INVALID_LEVEL, ntvfs->async_states->status)) { + ntvfs->async_states->status = NT_STATUS_INVALID_INFO_CLASS; + } + + SMB2SRV_CHECK_ASYNC_STATUS(op, struct smb2srv_setinfo_op); + + SMB2SRV_CHECK(smb2srv_setup_reply(op->req, 0x02, false, 0)); + + smb2srv_send_reply(req); +} + +static NTSTATUS smb2srv_setinfo_file(struct smb2srv_setinfo_op *op, uint8_t smb2_level) +{ + union smb_setfileinfo *io; + NTSTATUS status; + + io = talloc(op, union smb_setfileinfo); + NT_STATUS_HAVE_NO_MEMORY(io); + + /* the levels directly map to the passthru levels */ + io->generic.level = smb2_level + 1000; + io->generic.in.file.ntvfs = op->info->in.file.ntvfs; + + /* handle cases that don't map directly */ + if (io->generic.level == RAW_SFILEINFO_RENAME_INFORMATION) { + io->generic.level = RAW_SFILEINFO_RENAME_INFORMATION_SMB2; + } + + status = smbsrv_pull_passthru_sfileinfo(io, io->generic.level, io, + &op->info->in.blob, + STR_UNICODE, &op->req->in.bufinfo); + NT_STATUS_NOT_OK_RETURN(status); + + return ntvfs_setfileinfo(op->req->ntvfs, io); +} + +static NTSTATUS smb2srv_setinfo_fs(struct smb2srv_setinfo_op *op, uint8_t smb2_level) +{ + switch (smb2_level) { + case 0x02: + return NT_STATUS_NOT_IMPLEMENTED; + + case 0x06: + return NT_STATUS_ACCESS_DENIED; + + case 0x08: + return NT_STATUS_ACCESS_DENIED; + + case 0x0A: + return NT_STATUS_ACCESS_DENIED; + } + + return NT_STATUS_INVALID_INFO_CLASS; +} + +static NTSTATUS smb2srv_setinfo_security(struct smb2srv_setinfo_op *op, uint8_t smb2_level) +{ + union smb_setfileinfo *io; + enum ndr_err_code ndr_err; + + switch (smb2_level) { + case 0x00: + io = talloc(op, union smb_setfileinfo); + NT_STATUS_HAVE_NO_MEMORY(io); + + io->set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + io->set_secdesc.in.file.ntvfs = op->info->in.file.ntvfs; + io->set_secdesc.in.secinfo_flags = op->info->in.flags; + + io->set_secdesc.in.sd = talloc(io, struct security_descriptor); + NT_STATUS_HAVE_NO_MEMORY(io->set_secdesc.in.sd); + + ndr_err = ndr_pull_struct_blob(&op->info->in.blob, io, + io->set_secdesc.in.sd, + (ndr_pull_flags_fn_t)ndr_pull_security_descriptor); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ndr_map_error2ntstatus(ndr_err); + } + + return ntvfs_setfileinfo(op->req->ntvfs, io); + } + + return NT_STATUS_INVALID_INFO_CLASS; +} + +static NTSTATUS smb2srv_setinfo_backend(struct smb2srv_setinfo_op *op) +{ + uint8_t smb2_class; + uint8_t smb2_level; + + smb2_class = 0xFF & op->info->in.level; + smb2_level = 0xFF & (op->info->in.level>>8); + + switch (smb2_class) { + case SMB2_0_INFO_FILE: + return smb2srv_setinfo_file(op, smb2_level); + + case SMB2_0_INFO_FILESYSTEM: + return smb2srv_setinfo_fs(op, smb2_level); + + case SMB2_0_INFO_SECURITY: + return smb2srv_setinfo_security(op, smb2_level); + + case SMB2_0_INFO_QUOTA: + return NT_STATUS_NOT_SUPPORTED; + } + + return NT_STATUS_INVALID_PARAMETER; +} + +void smb2srv_setinfo_recv(struct smb2srv_request *req) +{ + struct smb2_setinfo *info; + struct smb2srv_setinfo_op *op; + + SMB2SRV_CHECK_BODY_SIZE(req, 0x20, true); + SMB2SRV_TALLOC_IO_PTR(info, struct smb2_setinfo); + /* this overwrites req->io_ptr !*/ + SMB2SRV_TALLOC_IO_PTR(op, struct smb2srv_setinfo_op); + op->req = req; + op->info = info; + SMB2SRV_SETUP_NTVFS_REQUEST(smb2srv_setinfo_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + info->in.level = SVAL(req->in.body, 0x02); + SMB2SRV_CHECK(smb2_pull_s32o16_blob(&req->in, info, req->in.body+0x04, &info->in.blob)); + info->in.flags = IVAL(req->in.body, 0x0C); + info->in.file.ntvfs = smb2srv_pull_handle(req, req->in.body, 0x10); + + SMB2SRV_CHECK_FILE_HANDLE(info->in.file.ntvfs); + SMB2SRV_CALL_NTVFS_BACKEND(smb2srv_setinfo_backend(op)); +} diff --git a/source4/smb_server/smb2/fileio.c b/source4/smb_server/smb2/fileio.c new file mode 100644 index 0000000..4153a42 --- /dev/null +++ b/source4/smb_server/smb2/fileio.c @@ -0,0 +1,547 @@ +/* + Unix SMB2 implementation. + + Copyright (C) Stefan Metzmacher 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "smb_server/smb_server.h" +#include "smb_server/smb2/smb2_server.h" +#include "ntvfs/ntvfs.h" +#include "libcli/raw/raw_proto.h" +#include "librpc/gen_ndr/ndr_security.h" + +static void smb2srv_create_send(struct ntvfs_request *ntvfs) +{ + struct smb2srv_request *req; + union smb_open *io; + DATA_BLOB blob; + + SMB2SRV_CHECK_ASYNC_STATUS(io, union smb_open); + + /* setup the blobs we should give in the reply */ + if (io->smb2.out.maximal_access != 0) { + uint32_t data[2]; + SIVAL(data, 0, 0); + SIVAL(data, 4, io->smb2.out.maximal_access); + SMB2SRV_CHECK(smb2_create_blob_add(req, &io->smb2.out.blobs, + SMB2_CREATE_TAG_MXAC, + data_blob_const(data, 8))); + } + + if (IVAL(io->smb2.out.on_disk_id, 0) != 0) { + SMB2SRV_CHECK(smb2_create_blob_add(req, &io->smb2.out.blobs, + SMB2_CREATE_TAG_QFID, + data_blob_const(io->smb2.out.on_disk_id, 32))); + } + + SMB2SRV_CHECK(smb2_create_blob_push(req, &blob, io->smb2.out.blobs)); + SMB2SRV_CHECK(smb2srv_setup_reply(req, 0x58, true, blob.length)); + + SCVAL(req->out.body, 0x02, io->smb2.out.oplock_level); + SCVAL(req->out.body, 0x03, io->smb2.out.reserved); + SIVAL(req->out.body, 0x04, io->smb2.out.create_action); + SBVAL(req->out.body, 0x08, io->smb2.out.create_time); + SBVAL(req->out.body, 0x10, io->smb2.out.access_time); + SBVAL(req->out.body, 0x18, io->smb2.out.write_time); + SBVAL(req->out.body, 0x20, io->smb2.out.change_time); + SBVAL(req->out.body, 0x28, io->smb2.out.alloc_size); + SBVAL(req->out.body, 0x30, io->smb2.out.size); + SIVAL(req->out.body, 0x38, io->smb2.out.file_attr); + SIVAL(req->out.body, 0x3C, io->smb2.out.reserved2); + smb2srv_push_handle(req->out.body, 0x40, io->smb2.out.file.ntvfs); + SMB2SRV_CHECK(smb2_push_o32s32_blob(&req->out, 0x50, blob)); + + /* also setup the chained file handle */ + req->chained_file_handle = req->_chained_file_handle; + smb2srv_push_handle(req->chained_file_handle, 0, io->smb2.out.file.ntvfs); + + smb2srv_send_reply(req); +} + +void smb2srv_create_recv(struct smb2srv_request *req) +{ + union smb_open *io; + DATA_BLOB blob; + int i; + + SMB2SRV_CHECK_BODY_SIZE(req, 0x38, true); + SMB2SRV_TALLOC_IO_PTR(io, union smb_open); + SMB2SRV_SETUP_NTVFS_REQUEST(smb2srv_create_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + ZERO_STRUCT(io->smb2.in); + io->smb2.level = RAW_OPEN_SMB2; + io->smb2.in.security_flags = CVAL(req->in.body, 0x02); + io->smb2.in.oplock_level = CVAL(req->in.body, 0x03); + io->smb2.in.impersonation_level = IVAL(req->in.body, 0x04); + io->smb2.in.create_flags = BVAL(req->in.body, 0x08); + io->smb2.in.reserved = BVAL(req->in.body, 0x10); + io->smb2.in.desired_access = IVAL(req->in.body, 0x18); + io->smb2.in.file_attributes = IVAL(req->in.body, 0x1C); + io->smb2.in.share_access = IVAL(req->in.body, 0x20); + io->smb2.in.create_disposition = IVAL(req->in.body, 0x24); + io->smb2.in.create_options = IVAL(req->in.body, 0x28); + SMB2SRV_CHECK(smb2_pull_o16s16_string(&req->in, io, req->in.body+0x2C, &io->smb2.in.fname)); + SMB2SRV_CHECK(smb2_pull_o32s32_blob(&req->in, io, req->in.body+0x30, &blob)); + SMB2SRV_CHECK(smb2_create_blob_parse(io, blob, &io->smb2.in.blobs)); + + /* interpret the parsed tags that a server needs to respond to */ + for (i=0;i<io->smb2.in.blobs.num_blobs;i++) { + if (strcmp(io->smb2.in.blobs.blobs[i].tag, SMB2_CREATE_TAG_EXTA) == 0) { + SMB2SRV_CHECK(ea_pull_list_chained(&io->smb2.in.blobs.blobs[i].data, io, + &io->smb2.in.eas.num_eas, + &io->smb2.in.eas.eas)); + } + if (strcmp(io->smb2.in.blobs.blobs[i].tag, SMB2_CREATE_TAG_SECD) == 0) { + enum ndr_err_code ndr_err; + io->smb2.in.sec_desc = talloc(io, struct security_descriptor); + if (io->smb2.in.sec_desc == NULL) { + smb2srv_send_error(req, NT_STATUS_NO_MEMORY); + return; + } + ndr_err = ndr_pull_struct_blob(&io->smb2.in.blobs.blobs[i].data, io, + io->smb2.in.sec_desc, + (ndr_pull_flags_fn_t)ndr_pull_security_descriptor); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + smb2srv_send_error(req, ndr_map_error2ntstatus(ndr_err)); + return; + } + } + if (strcmp(io->smb2.in.blobs.blobs[i].tag, SMB2_CREATE_TAG_DHNQ) == 0) { + io->smb2.in.durable_open = true; + } + if (strcmp(io->smb2.in.blobs.blobs[i].tag, SMB2_CREATE_TAG_DHNC) == 0) { + if (io->smb2.in.blobs.blobs[i].data.length != 16) { + smb2srv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + io->smb2.in.durable_handle = talloc(io, struct smb2_handle); + if (io->smb2.in.durable_handle == NULL) { + smb2srv_send_error(req, NT_STATUS_NO_MEMORY); + return; + } + smb2_pull_handle(io->smb2.in.blobs.blobs[i].data.data, io->smb2.in.durable_handle); + } + if (strcmp(io->smb2.in.blobs.blobs[i].tag, SMB2_CREATE_TAG_ALSI) == 0) { + if (io->smb2.in.blobs.blobs[i].data.length != 8) { + smb2srv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + io->smb2.in.alloc_size = BVAL(io->smb2.in.blobs.blobs[i].data.data, 0); + } + if (strcmp(io->smb2.in.blobs.blobs[i].tag, SMB2_CREATE_TAG_MXAC) == 0) { + io->smb2.in.query_maximal_access = true; + } + if (strcmp(io->smb2.in.blobs.blobs[i].tag, SMB2_CREATE_TAG_TWRP) == 0) { + if (io->smb2.in.blobs.blobs[i].data.length != 8) { + smb2srv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + io->smb2.in.timewarp = BVAL(io->smb2.in.blobs.blobs[i].data.data, 0); + } + if (strcmp(io->smb2.in.blobs.blobs[i].tag, SMB2_CREATE_TAG_QFID) == 0) { + io->smb2.in.query_on_disk_id = true; + } + } + + /* the VFS backend does not yet handle NULL filenames */ + if (io->smb2.in.fname == NULL) { + io->smb2.in.fname = ""; + } + + SMB2SRV_CALL_NTVFS_BACKEND(ntvfs_open(req->ntvfs, io)); +} + +static void smb2srv_close_send(struct ntvfs_request *ntvfs) +{ + struct smb2srv_request *req; + union smb_close *io; + + SMB2SRV_CHECK_ASYNC_STATUS(io, union smb_close); + SMB2SRV_CHECK(smb2srv_setup_reply(req, 0x3C, false, 0)); + + SSVAL(req->out.body, 0x02, io->smb2.out.flags); + SIVAL(req->out.body, 0x04, io->smb2.out._pad); + SBVAL(req->out.body, 0x08, io->smb2.out.create_time); + SBVAL(req->out.body, 0x10, io->smb2.out.access_time); + SBVAL(req->out.body, 0x18, io->smb2.out.write_time); + SBVAL(req->out.body, 0x20, io->smb2.out.change_time); + SBVAL(req->out.body, 0x28, io->smb2.out.alloc_size); + SBVAL(req->out.body, 0x30, io->smb2.out.size); + SIVAL(req->out.body, 0x38, io->smb2.out.file_attr); + + /* also destroy the chained file handle */ + req->chained_file_handle = NULL; + memset(req->_chained_file_handle, 0, sizeof(req->_chained_file_handle)); + + smb2srv_send_reply(req); +} + +void smb2srv_close_recv(struct smb2srv_request *req) +{ + union smb_close *io; + + SMB2SRV_CHECK_BODY_SIZE(req, 0x18, false); + SMB2SRV_TALLOC_IO_PTR(io, union smb_close); + SMB2SRV_SETUP_NTVFS_REQUEST(smb2srv_close_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + io->smb2.level = RAW_CLOSE_SMB2; + io->smb2.in.flags = SVAL(req->in.body, 0x02); + io->smb2.in._pad = IVAL(req->in.body, 0x04); + io->smb2.in.file.ntvfs = smb2srv_pull_handle(req, req->in.body, 0x08); + + SMB2SRV_CHECK_FILE_HANDLE(io->smb2.in.file.ntvfs); + SMB2SRV_CALL_NTVFS_BACKEND(ntvfs_close(req->ntvfs, io)); +} + +static void smb2srv_flush_send(struct ntvfs_request *ntvfs) +{ + struct smb2srv_request *req; + union smb_flush *io; + + SMB2SRV_CHECK_ASYNC_STATUS(io, union smb_flush); + SMB2SRV_CHECK(smb2srv_setup_reply(req, 0x04, false, 0)); + + SSVAL(req->out.body, 0x02, io->smb2.out.reserved); + + smb2srv_send_reply(req); +} + +void smb2srv_flush_recv(struct smb2srv_request *req) +{ + union smb_flush *io; + + SMB2SRV_CHECK_BODY_SIZE(req, 0x18, false); + SMB2SRV_TALLOC_IO_PTR(io, union smb_flush); + SMB2SRV_SETUP_NTVFS_REQUEST(smb2srv_flush_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + io->smb2.level = RAW_FLUSH_SMB2; + io->smb2.in.reserved1 = SVAL(req->in.body, 0x02); + io->smb2.in.reserved2 = IVAL(req->in.body, 0x04); + io->smb2.in.file.ntvfs = smb2srv_pull_handle(req, req->in.body, 0x08); + + SMB2SRV_CHECK_FILE_HANDLE(io->smb2.in.file.ntvfs); + SMB2SRV_CALL_NTVFS_BACKEND(ntvfs_flush(req->ntvfs, io)); +} + +static void smb2srv_read_send(struct ntvfs_request *ntvfs) +{ + struct smb2srv_request *req; + union smb_read *io; + + SMB2SRV_CHECK_ASYNC_STATUS(io, union smb_read); + SMB2SRV_CHECK(smb2srv_setup_reply(req, 0x10, true, io->smb2.out.data.length)); + + /* TODO: avoid the memcpy */ + SMB2SRV_CHECK(smb2_push_o16s32_blob(&req->out, 0x02, io->smb2.out.data)); + SIVAL(req->out.body, 0x08, io->smb2.out.remaining); + SIVAL(req->out.body, 0x0C, io->smb2.out.reserved); + + smb2srv_send_reply(req); +} + +void smb2srv_read_recv(struct smb2srv_request *req) +{ + union smb_read *io; + + SMB2SRV_CHECK_BODY_SIZE(req, 0x30, true); + + /* MS-SMB2 2.2.19 read must have a single byte of zero */ + if (req->in.body_size - req->in.body_fixed < 1) { + smb2srv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + SMB2SRV_TALLOC_IO_PTR(io, union smb_read); + SMB2SRV_SETUP_NTVFS_REQUEST(smb2srv_read_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + io->smb2.level = RAW_READ_SMB2; + io->smb2.in._pad = SVAL(req->in.body, 0x02); + io->smb2.in.length = IVAL(req->in.body, 0x04); + io->smb2.in.offset = BVAL(req->in.body, 0x08); + io->smb2.in.file.ntvfs = smb2srv_pull_handle(req, req->in.body, 0x10); + io->smb2.in.min_count = IVAL(req->in.body, 0x20); + io->smb2.in.channel = IVAL(req->in.body, 0x24); + io->smb2.in.remaining = IVAL(req->in.body, 0x28); + io->smb2.in.channel_offset = SVAL(req->in.body, 0x2C); + io->smb2.in.channel_length = SVAL(req->in.body, 0x2E); + + SMB2SRV_CHECK_FILE_HANDLE(io->smb2.in.file.ntvfs); + + /* preallocate the buffer for the backends */ + io->smb2.out.data = data_blob_talloc(io, NULL, io->smb2.in.length); + if (io->smb2.out.data.length != io->smb2.in.length) { + SMB2SRV_CHECK(NT_STATUS_NO_MEMORY); + } + + SMB2SRV_CALL_NTVFS_BACKEND(ntvfs_read(req->ntvfs, io)); +} + +static void smb2srv_write_send(struct ntvfs_request *ntvfs) +{ + struct smb2srv_request *req; + union smb_write *io; + + SMB2SRV_CHECK_ASYNC_STATUS(io, union smb_write); + SMB2SRV_CHECK(smb2srv_setup_reply(req, 0x10, true, 0)); + + SSVAL(req->out.body, 0x02, io->smb2.out._pad); + SIVAL(req->out.body, 0x04, io->smb2.out.nwritten); + SBVAL(req->out.body, 0x08, io->smb2.out.unknown1); + + smb2srv_send_reply(req); +} + +void smb2srv_write_recv(struct smb2srv_request *req) +{ + union smb_write *io; + + SMB2SRV_CHECK_BODY_SIZE(req, 0x30, true); + SMB2SRV_TALLOC_IO_PTR(io, union smb_write); + SMB2SRV_SETUP_NTVFS_REQUEST(smb2srv_write_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + /* TODO: avoid the memcpy */ + io->smb2.level = RAW_WRITE_SMB2; + SMB2SRV_CHECK(smb2_pull_o16s32_blob(&req->in, io, req->in.body+0x02, &io->smb2.in.data)); + io->smb2.in.offset = BVAL(req->in.body, 0x08); + io->smb2.in.file.ntvfs = smb2srv_pull_handle(req, req->in.body, 0x10); + io->smb2.in.unknown1 = BVAL(req->in.body, 0x20); + io->smb2.in.unknown2 = BVAL(req->in.body, 0x28); + + SMB2SRV_CHECK_FILE_HANDLE(io->smb2.in.file.ntvfs); + SMB2SRV_CALL_NTVFS_BACKEND(ntvfs_write(req->ntvfs, io)); +} + +static void smb2srv_lock_send(struct ntvfs_request *ntvfs) +{ + struct smb2srv_request *req; + union smb_lock *io; + + SMB2SRV_CHECK_ASYNC_STATUS_ERR(io, union smb_lock); + SMB2SRV_CHECK(smb2srv_setup_reply(req, 0x04, false, 0)); + + SSVAL(req->out.body, 0x02, io->smb2.out.reserved); + + smb2srv_send_reply(req); +} + +void smb2srv_lock_recv(struct smb2srv_request *req) +{ + union smb_lock *io; + int i; + + SMB2SRV_CHECK_BODY_SIZE(req, 0x30, false); + SMB2SRV_TALLOC_IO_PTR(io, union smb_lock); + SMB2SRV_SETUP_NTVFS_REQUEST(smb2srv_lock_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + io->smb2.level = RAW_LOCK_SMB2; + io->smb2.in.lock_count = SVAL(req->in.body, 0x02); + io->smb2.in.lock_sequence = IVAL(req->in.body, 0x04); + io->smb2.in.file.ntvfs = smb2srv_pull_handle(req, req->in.body, 0x08); + if (req->in.body_size < 24 + 24*(uint64_t)io->smb2.in.lock_count) { + DEBUG(0,("%s: lock buffer too small\n", __location__)); + smb2srv_send_error(req, NT_STATUS_FOOBAR); + return; + } + io->smb2.in.locks = talloc_array(io, struct smb2_lock_element, + io->smb2.in.lock_count); + if (io->smb2.in.locks == NULL) { + smb2srv_send_error(req, NT_STATUS_NO_MEMORY); + return; + } + + for (i=0;i<io->smb2.in.lock_count;i++) { + io->smb2.in.locks[i].offset = BVAL(req->in.body, 24 + i*24); + io->smb2.in.locks[i].length = BVAL(req->in.body, 32 + i*24); + io->smb2.in.locks[i].flags = IVAL(req->in.body, 40 + i*24); + io->smb2.in.locks[i].reserved = IVAL(req->in.body, 44 + i*24); + } + + SMB2SRV_CHECK_FILE_HANDLE(io->smb2.in.file.ntvfs); + SMB2SRV_CALL_NTVFS_BACKEND(ntvfs_lock(req->ntvfs, io)); +} + +static void smb2srv_ioctl_send(struct ntvfs_request *ntvfs) +{ + struct smb2srv_request *req; + union smb_ioctl *io; + + SMB2SRV_CHECK_ASYNC_STATUS_ERR(io, union smb_ioctl); + SMB2SRV_CHECK(smb2srv_setup_reply(req, 0x30, true, 0)); + + SSVAL(req->out.body, 0x02, io->smb2.out.reserved); + SIVAL(req->out.body, 0x04, io->smb2.out.function); + if (io->smb2.level == RAW_IOCTL_SMB2_NO_HANDLE) { + struct smb2_handle h; + h.data[0] = UINT64_MAX; + h.data[1] = UINT64_MAX; + smb2_push_handle(req->out.body + 0x08, &h); + } else { + smb2srv_push_handle(req->out.body, 0x08,io->smb2.in.file.ntvfs); + } + SMB2SRV_CHECK(smb2_push_o32s32_blob(&req->out, 0x18, io->smb2.out.in)); + SMB2SRV_CHECK(smb2_push_o32s32_blob(&req->out, 0x20, io->smb2.out.out)); + SIVAL(req->out.body, 0x28, io->smb2.out.flags); + SIVAL(req->out.body, 0x2C, io->smb2.out.reserved2); + + smb2srv_send_reply(req); +} + +void smb2srv_ioctl_recv(struct smb2srv_request *req) +{ + union smb_ioctl *io; + struct smb2_handle h; + + SMB2SRV_CHECK_BODY_SIZE(req, 0x38, true); + SMB2SRV_TALLOC_IO_PTR(io, union smb_ioctl); + SMB2SRV_SETUP_NTVFS_REQUEST(smb2srv_ioctl_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + /* TODO: avoid the memcpy */ + io->smb2.in.reserved = SVAL(req->in.body, 0x02); + io->smb2.in.function = IVAL(req->in.body, 0x04); + /* file handle ... */ + SMB2SRV_CHECK(smb2_pull_o32s32_blob(&req->in, io, req->in.body+0x18, &io->smb2.in.out)); + io->smb2.in.max_input_response = IVAL(req->in.body, 0x20); + SMB2SRV_CHECK(smb2_pull_o32s32_blob(&req->in, io, req->in.body+0x24, &io->smb2.in.in)); + io->smb2.in.max_output_response = IVAL(req->in.body, 0x2C); + io->smb2.in.flags = IVAL(req->in.body, 0x30); + io->smb2.in.reserved2 = IVAL(req->in.body, 0x34); + + smb2_pull_handle(req->in.body + 0x08, &h); + if (h.data[0] == UINT64_MAX && h.data[1] == UINT64_MAX) { + io->smb2.level = RAW_IOCTL_SMB2_NO_HANDLE; + } else { + io->smb2.level = RAW_IOCTL_SMB2; + io->smb2.in.file.ntvfs = smb2srv_pull_handle(req, req->in.body, 0x08); + SMB2SRV_CHECK_FILE_HANDLE(io->smb2.in.file.ntvfs); + } + + SMB2SRV_CALL_NTVFS_BACKEND(ntvfs_ioctl(req->ntvfs, io)); +} + +static void smb2srv_notify_send(struct ntvfs_request *ntvfs) +{ + struct smb2srv_request *req; + union smb_notify *io; + size_t size = 0; + int i; + uint8_t *p; + DATA_BLOB blob = data_blob(NULL, 0); + + SMB2SRV_CHECK_ASYNC_STATUS(io, union smb_notify); + SMB2SRV_CHECK(smb2srv_setup_reply(req, 0x08, true, 0)); + +#define MAX_BYTES_PER_CHAR 3 + + /* work out how big the reply buffer could be */ + for (i=0;i<io->smb2.out.num_changes;i++) { + size += 12 + 3 + (1+strlen(io->smb2.out.changes[i].name.s)) * MAX_BYTES_PER_CHAR; + } + + blob = data_blob_talloc(req, NULL, size); + if (size > 0 && !blob.data) { + SMB2SRV_CHECK(NT_STATUS_NO_MEMORY); + } + + p = blob.data; + + /* construct the changes buffer */ + for (i=0;i<io->smb2.out.num_changes;i++) { + uint32_t ofs; + ssize_t len; + + SIVAL(p, 4, io->smb2.out.changes[i].action); + len = push_string(p + 12, io->smb2.out.changes[i].name.s, + blob.length - (p+12 - blob.data), STR_UNICODE); + SIVAL(p, 8, len); + + ofs = len + 12; + + if (ofs & 3) { + int pad = 4 - (ofs & 3); + memset(p+ofs, 0, pad); + ofs += pad; + } + + if (i == io->smb2.out.num_changes-1) { + SIVAL(p, 0, 0); + } else { + SIVAL(p, 0, ofs); + } + + p += ofs; + } + + blob.length = p - blob.data; + + SMB2SRV_CHECK(smb2_push_o16s32_blob(&req->out, 0x02, blob)); + + smb2srv_send_reply(req); +} + +void smb2srv_notify_recv(struct smb2srv_request *req) +{ + union smb_notify *io; + + SMB2SRV_CHECK_BODY_SIZE(req, 0x20, false); + SMB2SRV_TALLOC_IO_PTR(io, union smb_notify); + SMB2SRV_SETUP_NTVFS_REQUEST(smb2srv_notify_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + io->smb2.level = RAW_NOTIFY_SMB2; + io->smb2.in.recursive = SVAL(req->in.body, 0x02); + io->smb2.in.buffer_size = IVAL(req->in.body, 0x04); + io->smb2.in.file.ntvfs = smb2srv_pull_handle(req, req->in.body, 0x08); + io->smb2.in.completion_filter = IVAL(req->in.body, 0x18); + io->smb2.in.unknown = BVAL(req->in.body, 0x1C); + + SMB2SRV_CHECK_FILE_HANDLE(io->smb2.in.file.ntvfs); + SMB2SRV_CALL_NTVFS_BACKEND(ntvfs_notify(req->ntvfs, io)); +} + +static void smb2srv_break_send(struct ntvfs_request *ntvfs) +{ + struct smb2srv_request *req; + union smb_lock *io; + + SMB2SRV_CHECK_ASYNC_STATUS_ERR(io, union smb_lock); + SMB2SRV_CHECK(smb2srv_setup_reply(req, 0x18, false, 0)); + + SCVAL(req->out.body, 0x02, io->smb2_break.out.oplock_level); + SCVAL(req->out.body, 0x03, io->smb2_break.out.reserved); + SIVAL(req->out.body, 0x04, io->smb2_break.out.reserved2); + smb2srv_push_handle(req->out.body, 0x08,io->smb2_break.out.file.ntvfs); + + smb2srv_send_reply(req); +} + +void smb2srv_break_recv(struct smb2srv_request *req) +{ + union smb_lock *io; + + SMB2SRV_CHECK_BODY_SIZE(req, 0x18, false); + SMB2SRV_TALLOC_IO_PTR(io, union smb_lock); + SMB2SRV_SETUP_NTVFS_REQUEST(smb2srv_break_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + io->smb2_break.level = RAW_LOCK_SMB2_BREAK; + io->smb2_break.in.oplock_level = CVAL(req->in.body, 0x02); + io->smb2_break.in.reserved = CVAL(req->in.body, 0x03); + io->smb2_break.in.reserved2 = IVAL(req->in.body, 0x04); + io->smb2_break.in.file.ntvfs = smb2srv_pull_handle(req, req->in.body, 0x08); + + SMB2SRV_CHECK_FILE_HANDLE(io->smb2_break.in.file.ntvfs); + SMB2SRV_CALL_NTVFS_BACKEND(ntvfs_lock(req->ntvfs, io)); +} diff --git a/source4/smb_server/smb2/find.c b/source4/smb_server/smb2/find.c new file mode 100644 index 0000000..17f09e1 --- /dev/null +++ b/source4/smb_server/smb2/find.c @@ -0,0 +1,167 @@ +/* + Unix SMB/CIFS implementation. + SMB2 Find + Copyright (C) Andrew Tridgell 2003 + Copyright (c) Stefan Metzmacher 2006 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +/* + This file handles the parsing of transact2 requests +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "smb_server/smb_server.h" +#include "smb_server/smb2/smb2_server.h" +#include "ntvfs/ntvfs.h" + + +/* a structure to encapsulate the state information about an in-progress ffirst/fnext operation */ +struct smb2srv_find_state { + struct smb2srv_request *req; + struct smb2_find *info; + union smb_search_first *ff; + union smb_search_next *fn; + uint32_t last_entry_offset; +}; + +/* callback function for SMB2 Find */ +static bool smb2srv_find_callback(void *private_data, const union smb_search_data *file) +{ + struct smb2srv_find_state *state = talloc_get_type(private_data, struct smb2srv_find_state); + struct smb2_find *info = state->info; + uint32_t old_length; + NTSTATUS status; + + old_length = info->out.blob.length; + + status = smbsrv_push_passthru_search(state, &info->out.blob, info->data_level, file, STR_UNICODE); + if (!NT_STATUS_IS_OK(status) || + info->out.blob.length > info->in.max_response_size) { + /* restore the old length and tell the backend to stop */ + smbsrv_blob_grow_data(state, &info->out.blob, old_length); + return false; + } + + state->last_entry_offset = old_length; + + return true; +} + +static void smb2srv_find_send(struct ntvfs_request *ntvfs) +{ + struct smb2srv_request *req; + struct smb2srv_find_state *state; + + SMB2SRV_CHECK_ASYNC_STATUS(state, struct smb2srv_find_state); + SMB2SRV_CHECK(smb2srv_setup_reply(req, 0x08, true, state->info->out.blob.length)); + + if (state->info->out.blob.length > 0) { + SIVAL(state->info->out.blob.data + state->last_entry_offset, 0, 0); + } + + SMB2SRV_CHECK(smb2_push_o16s32_blob(&req->out, 0x02, state->info->out.blob)); + + smb2srv_send_reply(req); +} + +static NTSTATUS smb2srv_find_backend(struct smb2srv_find_state *state) +{ + struct smb2_find *info = state->info; + + switch (info->in.level) { + case SMB2_FIND_DIRECTORY_INFO: + info->data_level = RAW_SEARCH_DATA_DIRECTORY_INFO; + break; + + case SMB2_FIND_FULL_DIRECTORY_INFO: + info->data_level = RAW_SEARCH_DATA_FULL_DIRECTORY_INFO; + break; + + case SMB2_FIND_BOTH_DIRECTORY_INFO: + info->data_level = RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO; + break; + + case SMB2_FIND_NAME_INFO: + info->data_level = RAW_SEARCH_DATA_NAME_INFO; + break; + + case SMB2_FIND_ID_BOTH_DIRECTORY_INFO: + info->data_level = RAW_SEARCH_DATA_ID_BOTH_DIRECTORY_INFO; + break; + + case SMB2_FIND_ID_FULL_DIRECTORY_INFO: + info->data_level = RAW_SEARCH_DATA_ID_FULL_DIRECTORY_INFO; + break; + + default: + return NT_STATUS_FOOBAR; + } + + if (info->in.continue_flags & SMB2_CONTINUE_FLAG_REOPEN) { + state->ff = talloc(state, union smb_search_first); + NT_STATUS_HAVE_NO_MEMORY(state->ff); + + state->ff->smb2 = *info; + state->info = &state->ff->smb2; + ZERO_STRUCT(state->ff->smb2.out); + + return ntvfs_search_first(state->req->ntvfs, state->ff, state, smb2srv_find_callback); + } else { + state->fn = talloc(state, union smb_search_next); + NT_STATUS_HAVE_NO_MEMORY(state->fn); + + state->fn->smb2 = *info; + state->info = &state->fn->smb2; + ZERO_STRUCT(state->fn->smb2.out); + + return ntvfs_search_next(state->req->ntvfs, state->fn, state, smb2srv_find_callback); + } +} + +void smb2srv_find_recv(struct smb2srv_request *req) +{ + struct smb2srv_find_state *state; + struct smb2_find *info; + + SMB2SRV_CHECK_BODY_SIZE(req, 0x20, true); + SMB2SRV_TALLOC_IO_PTR(info, struct smb2_find); + /* this overwrites req->io_ptr !*/ + SMB2SRV_TALLOC_IO_PTR(state, struct smb2srv_find_state); + state->req = req; + state->info = info; + state->ff = NULL; + state->fn = NULL; + state->last_entry_offset= 0; + SMB2SRV_SETUP_NTVFS_REQUEST(smb2srv_find_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + info->level = RAW_SEARCH_SMB2; + info->data_level = RAW_SEARCH_DATA_GENERIC;/* will be overwritten later */ + info->in.level = CVAL(req->in.body, 0x02); + info->in.continue_flags = CVAL(req->in.body, 0x03); + info->in.file_index = IVAL(req->in.body, 0x04); + info->in.file.ntvfs = smb2srv_pull_handle(req, req->in.body, 0x08); + SMB2SRV_CHECK(smb2_pull_o16s16_string(&req->in, info, req->in.body+0x18, &info->in.pattern)); + info->in.max_response_size = IVAL(req->in.body, 0x1C); + + /* the VFS backend does not yet handle NULL patterns */ + if (info->in.pattern == NULL) { + info->in.pattern = ""; + } + + SMB2SRV_CHECK_FILE_HANDLE(info->in.file.ntvfs); + SMB2SRV_CALL_NTVFS_BACKEND(smb2srv_find_backend(state)); +} diff --git a/source4/smb_server/smb2/keepalive.c b/source4/smb_server/smb2/keepalive.c new file mode 100644 index 0000000..cd53778 --- /dev/null +++ b/source4/smb_server/smb2/keepalive.c @@ -0,0 +1,71 @@ +/* + Unix SMB2 implementation. + + Copyright (C) Stefan Metzmacher 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "smb_server/smb_server.h" +#include "smb_server/smb2/smb2_server.h" + +static NTSTATUS smb2srv_keepalive_backend(struct smb2srv_request *req) +{ + /* TODO: maybe update some flags on the connection struct */ + return NT_STATUS_OK; +} + +static void smb2srv_keepalive_send(struct smb2srv_request *req) +{ + NTSTATUS status; + + if (NT_STATUS_IS_ERR(req->status)) { + smb2srv_send_error(req, req->status); + return; + } + + status = smb2srv_setup_reply(req, 0x04, false, 0); + if (!NT_STATUS_IS_OK(status)) { + smbsrv_terminate_connection(req->smb_conn, nt_errstr(status)); + talloc_free(req); + return; + } + + SSVAL(req->out.body, 0x02, 0); + + smb2srv_send_reply(req); +} + +void smb2srv_keepalive_recv(struct smb2srv_request *req) +{ + if (req->in.body_size != 0x04) { + smb2srv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + + if (SVAL(req->in.body, 0x00) != 0x04) { + smb2srv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + + req->status = smb2srv_keepalive_backend(req); + + if (req->control_flags & SMB2SRV_REQ_CTRL_FLAG_NOT_REPLY) { + talloc_free(req); + return; + } + smb2srv_keepalive_send(req); +} diff --git a/source4/smb_server/smb2/negprot.c b/source4/smb_server/smb2/negprot.c new file mode 100644 index 0000000..048d7b4 --- /dev/null +++ b/source4/smb_server/smb2/negprot.c @@ -0,0 +1,327 @@ +/* + Unix SMB2 implementation. + + Copyright (C) Andrew Bartlett 2001-2005 + Copyright (C) Stefan Metzmacher 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "auth/credentials/credentials.h" +#include "auth/auth.h" +#include "auth/gensec/gensec.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "smb_server/smb_server.h" +#include "smb_server/smb2/smb2_server.h" +#include "samba/service_stream.h" +#include "param/param.h" + +static NTSTATUS smb2srv_negprot_secblob(struct smb2srv_request *req, DATA_BLOB *_blob) +{ + struct gensec_security *gensec_security; + DATA_BLOB null_data_blob = data_blob(NULL, 0); + DATA_BLOB blob; + NTSTATUS nt_status; + struct cli_credentials *server_credentials; + + server_credentials = + cli_credentials_init_server(req, req->smb_conn->lp_ctx); + if (server_credentials == NULL) { + DBG_DEBUG("Failed to obtain server credentials, " + "perhaps a standalone server?\n"); + /* + * Create anon server credentials for for the + * spoolss.notify test. + */ + server_credentials = cli_credentials_init_anon(req); + if (server_credentials == NULL) { + smbsrv_terminate_connection(req->smb_conn, + "Failed to init server credentials\n"); + return NT_STATUS_NO_MEMORY; + } + } + + req->smb_conn->negotiate.server_credentials = talloc_steal(req->smb_conn, server_credentials); + + nt_status = samba_server_gensec_start(req, + req->smb_conn->connection->event.ctx, + req->smb_conn->connection->msg_ctx, + req->smb_conn->lp_ctx, + server_credentials, + "cifs", + &gensec_security); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(0, ("Failed to start GENSEC: %s\n", nt_errstr(nt_status))); + smbsrv_terminate_connection(req->smb_conn, "Failed to start GENSEC\n"); + return nt_status; + } + + gensec_set_target_service(gensec_security, "cifs"); + + gensec_set_credentials(gensec_security, server_credentials); + + nt_status = gensec_start_mech_by_oid(gensec_security, GENSEC_OID_SPNEGO); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(0, ("Failed to start SPNEGO: %s\n", nt_errstr(nt_status))); + smbsrv_terminate_connection(req->smb_conn, "Failed to start SPNEGO\n"); + return nt_status; + } + + nt_status = gensec_update(gensec_security, req, + null_data_blob, &blob); + if (!NT_STATUS_IS_OK(nt_status) && !NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + DEBUG(0, ("Failed to get SPNEGO to give us the first token: %s\n", nt_errstr(nt_status))); + smbsrv_terminate_connection(req->smb_conn, "Failed to start SPNEGO - no first token\n"); + return nt_status; + } + + *_blob = blob; + return NT_STATUS_OK; +} + +static NTSTATUS smb2srv_negprot_backend(struct smb2srv_request *req, struct smb2_negprot *io) +{ + NTSTATUS status; + struct timeval current_time; + struct timeval boot_time; + uint16_t i; + uint16_t dialect = 0; + enum smb_signing_setting signing_setting; + struct loadparm_context *lp_ctx = req->smb_conn->lp_ctx; + + /* we only do one dialect for now */ + if (io->in.dialect_count < 1) { + return NT_STATUS_NOT_SUPPORTED; + } + for (i=0; i < io->in.dialect_count; i++) { + dialect = io->in.dialects[i]; + if (dialect == SMB2_DIALECT_REVISION_202) { + break; + } + } + if (dialect != SMB2_DIALECT_REVISION_202) { + DEBUG(0,("Got unexpected SMB2 dialect %u\n", dialect)); + return NT_STATUS_NOT_SUPPORTED; + } + + req->smb_conn->negotiate.protocol = PROTOCOL_SMB2_02; + + current_time = timeval_current(); /* TODO: handle timezone?! */ + boot_time = timeval_current(); /* TODO: fix me */ + + ZERO_STRUCT(io->out); + + signing_setting = lpcfg_server_signing(lp_ctx); + if (signing_setting == SMB_SIGNING_DEFAULT) { + /* + * If we are a domain controller, SMB signing is + * really important, as it can prevent a number of + * attacks on communications between us and the + * clients + * + * However, it really sucks (no sendfile, CPU + * overhead) performance-wise when used on a + * file server, so disable it by default + * on non-DCs + */ + + if (lpcfg_server_role(lp_ctx) >= ROLE_ACTIVE_DIRECTORY_DC) { + signing_setting = SMB_SIGNING_REQUIRED; + } else { + signing_setting = SMB_SIGNING_OFF; + } + } + + switch (signing_setting) { + case SMB_SIGNING_DEFAULT: + case SMB_SIGNING_IPC_DEFAULT: + smb_panic(__location__); + break; + case SMB_SIGNING_OFF: + io->out.security_mode = 0; + break; + case SMB_SIGNING_DESIRED: + case SMB_SIGNING_IF_REQUIRED: + io->out.security_mode = SMB2_NEGOTIATE_SIGNING_ENABLED; + break; + case SMB_SIGNING_REQUIRED: + io->out.security_mode = SMB2_NEGOTIATE_SIGNING_ENABLED | SMB2_NEGOTIATE_SIGNING_REQUIRED; + /* force signing on immediately */ + req->smb_conn->smb2_signing_required = true; + break; + } + io->out.dialect_revision = dialect; + io->out.capabilities = 0; + io->out.max_transact_size = lpcfg_parm_ulong(req->smb_conn->lp_ctx, NULL, + "smb2", "max transaction size", 0x10000); + io->out.max_read_size = lpcfg_parm_ulong(req->smb_conn->lp_ctx, NULL, + "smb2", "max read size", 0x10000); + io->out.max_write_size = lpcfg_parm_ulong(req->smb_conn->lp_ctx, NULL, + "smb2", "max write size", 0x10000); + io->out.system_time = timeval_to_nttime(¤t_time); + io->out.server_start_time = timeval_to_nttime(&boot_time); + io->out.reserved2 = 0; + status = smb2srv_negprot_secblob(req, &io->out.secblob); + NT_STATUS_NOT_OK_RETURN(status); + + return NT_STATUS_OK; +} + +static void smb2srv_negprot_send(struct smb2srv_request *req, struct smb2_negprot *io) +{ + NTSTATUS status; + + if (NT_STATUS_IS_ERR(req->status)) { + smb2srv_send_error(req, req->status); /* TODO: is this correct? */ + return; + } + + status = smb2srv_setup_reply(req, 0x40, true, io->out.secblob.length); + if (!NT_STATUS_IS_OK(status)) { + smbsrv_terminate_connection(req->smb_conn, nt_errstr(status)); + talloc_free(req); + return; + } + + SSVAL(req->out.body, 0x02, io->out.security_mode); + SIVAL(req->out.body, 0x04, io->out.dialect_revision); + SIVAL(req->out.body, 0x06, io->out.reserved); + status = smbcli_push_guid(req->out.body, 0x08, &io->out.server_guid); + if (!NT_STATUS_IS_OK(status)) { + smbsrv_terminate_connection(req->smb_conn, nt_errstr(status)); + talloc_free(req); + return; + } + SIVAL(req->out.body, 0x18, io->out.capabilities); + SIVAL(req->out.body, 0x1C, io->out.max_transact_size); + SIVAL(req->out.body, 0x20, io->out.max_read_size); + SIVAL(req->out.body, 0x24, io->out.max_write_size); + push_nttime(req->out.body, 0x28, io->out.system_time); + push_nttime(req->out.body, 0x30, io->out.server_start_time); + SIVAL(req->out.body, 0x3C, io->out.reserved2); + status = smb2_push_o16s16_blob(&req->out, 0x38, io->out.secblob); + if (!NT_STATUS_IS_OK(status)) { + smbsrv_terminate_connection(req->smb_conn, nt_errstr(status)); + talloc_free(req); + return; + } + + smb2srv_send_reply(req); +} + +void smb2srv_negprot_recv(struct smb2srv_request *req) +{ + struct smb2_negprot *io; + int i; + + if (req->in.body_size < 0x26) { + smbsrv_terminate_connection(req->smb_conn, "Bad body size in SMB2 negprot"); + return; + } + + io = talloc(req, struct smb2_negprot); + if (!io) { + smbsrv_terminate_connection(req->smb_conn, nt_errstr(NT_STATUS_NO_MEMORY)); + talloc_free(req); + return; + } + + io->in.dialect_count = SVAL(req->in.body, 0x02); + io->in.security_mode = SVAL(req->in.body, 0x04); + io->in.reserved = SVAL(req->in.body, 0x06); + io->in.capabilities = IVAL(req->in.body, 0x08); + req->status = smbcli_pull_guid(req->in.body, 0xC, &io->in.client_guid); + if (!NT_STATUS_IS_OK(req->status)) { + smbsrv_terminate_connection(req->smb_conn, "Bad GUID in SMB2 negprot"); + talloc_free(req); + return; + } + io->in.start_time = smbcli_pull_nttime(req->in.body, 0x1C); + + io->in.dialects = talloc_array(req, uint16_t, io->in.dialect_count); + if (io->in.dialects == NULL) { + smbsrv_terminate_connection(req->smb_conn, nt_errstr(NT_STATUS_NO_MEMORY)); + talloc_free(req); + return; + } + for (i=0;i<io->in.dialect_count;i++) { + io->in.dialects[i] = SVAL(req->in.body, 0x24+i*2); + } + + req->status = smb2srv_negprot_backend(req, io); + + if (req->control_flags & SMB2SRV_REQ_CTRL_FLAG_NOT_REPLY) { + talloc_free(req); + return; + } + smb2srv_negprot_send(req, io); +} + +/* + * reply to a SMB negprot request with dialect "SMB 2.002" + */ +void smb2srv_reply_smb_negprot(struct smbsrv_request *smb_req) +{ + struct smb2srv_request *req; + uint32_t body_fixed_size = 0x26; + + req = talloc_zero(smb_req->smb_conn, struct smb2srv_request); + if (!req) goto nomem; + req->smb_conn = smb_req->smb_conn; + req->request_time = smb_req->request_time; + talloc_steal(req, smb_req); + + req->in.size = NBT_HDR_SIZE+SMB2_HDR_BODY+body_fixed_size; + req->in.allocated = req->in.size; + req->in.buffer = talloc_array(req, uint8_t, req->in.allocated); + if (!req->in.buffer) goto nomem; + req->in.hdr = req->in.buffer + NBT_HDR_SIZE; + req->in.body = req->in.hdr + SMB2_HDR_BODY; + req->in.body_size = body_fixed_size; + req->in.dynamic = NULL; + + smb2srv_setup_bufinfo(req); + + SIVAL(req->in.hdr, 0, SMB2_MAGIC); + SSVAL(req->in.hdr, SMB2_HDR_LENGTH, SMB2_HDR_BODY); + SSVAL(req->in.hdr, SMB2_HDR_EPOCH, 0); + SIVAL(req->in.hdr, SMB2_HDR_STATUS, 0); + SSVAL(req->in.hdr, SMB2_HDR_OPCODE, SMB2_OP_NEGPROT); + SSVAL(req->in.hdr, SMB2_HDR_CREDIT, 0); + SIVAL(req->in.hdr, SMB2_HDR_FLAGS, 0); + SIVAL(req->in.hdr, SMB2_HDR_NEXT_COMMAND, 0); + SBVAL(req->in.hdr, SMB2_HDR_MESSAGE_ID, 0); + SIVAL(req->in.hdr, SMB2_HDR_PID, 0); + SIVAL(req->in.hdr, SMB2_HDR_TID, 0); + SBVAL(req->in.hdr, SMB2_HDR_SESSION_ID, 0); + memset(req->in.hdr+SMB2_HDR_SIGNATURE, 0, 16); + + /* this seems to be a bug, they use 0x24 but the length is 0x26 */ + SSVAL(req->in.body, 0x00, 0x24); + + SSVAL(req->in.body, 0x02, 1); + memset(req->in.body+0x04, 0, 32); + SSVAL(req->in.body, 0x24, SMB2_DIALECT_REVISION_202); + + smb2srv_negprot_recv(req); + return; +nomem: + smbsrv_terminate_connection(smb_req->smb_conn, nt_errstr(NT_STATUS_NO_MEMORY)); + talloc_free(req); + return; +} diff --git a/source4/smb_server/smb2/receive.c b/source4/smb_server/smb2/receive.c new file mode 100644 index 0000000..c4109bf --- /dev/null +++ b/source4/smb_server/smb2/receive.c @@ -0,0 +1,710 @@ +/* + Unix SMB2 implementation. + + Copyright (C) Andrew Tridgell 2005 + Copyright (C) Stefan Metzmacher 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/time.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "smb_server/smb_server.h" +#include "smb_server/smb2/smb2_server.h" +#include "samba/service_stream.h" +#include "lib/stream/packet.h" +#include "ntvfs/ntvfs.h" +#include "param/param.h" +#include "auth/auth.h" + + +/* fill in the bufinfo */ +void smb2srv_setup_bufinfo(struct smb2srv_request *req) +{ + req->in.bufinfo.mem_ctx = req; + req->in.bufinfo.flags = BUFINFO_FLAG_UNICODE | BUFINFO_FLAG_SMB2; + req->in.bufinfo.align_base = req->in.buffer; + if (req->in.dynamic) { + req->in.bufinfo.data = req->in.dynamic; + req->in.bufinfo.data_size = req->in.body_size - req->in.body_fixed; + } else { + req->in.bufinfo.data = NULL; + req->in.bufinfo.data_size = 0; + } +} + +static int smb2srv_request_destructor(struct smb2srv_request *req) +{ + DLIST_REMOVE(req->smb_conn->requests2.list, req); + if (req->pending_id) { + idr_remove(req->smb_conn->requests2.idtree_req, req->pending_id); + } + return 0; +} + +static int smb2srv_request_deny_destructor(struct smb2srv_request *req) +{ + return -1; +} + +struct smb2srv_request *smb2srv_init_request(struct smbsrv_connection *smb_conn) +{ + struct smb2srv_request *req; + + req = talloc_zero(smb_conn, struct smb2srv_request); + if (!req) return NULL; + + req->smb_conn = smb_conn; + + req->chained_session_id = UINT64_MAX; + req->chained_tree_id = UINT32_MAX; + + talloc_set_destructor(req, smb2srv_request_destructor); + + return req; +} + +NTSTATUS smb2srv_setup_reply(struct smb2srv_request *req, uint16_t body_fixed_size, + bool body_dynamic_present, uint32_t body_dynamic_size) +{ + uint32_t flags = IVAL(req->in.hdr, SMB2_HDR_FLAGS); + uint32_t pid = IVAL(req->in.hdr, SMB2_HDR_PID); + uint32_t tid = IVAL(req->in.hdr, SMB2_HDR_TID); + uint16_t credits = SVAL(req->in.hdr, SMB2_HDR_CREDIT); + + if (credits == 0) { + credits = 1; + } + + flags |= SMB2_HDR_FLAG_REDIRECT; + + if (req->pending_id) { + flags |= SMB2_HDR_FLAG_ASYNC; + pid = req->pending_id; + tid = 0; + credits = 0; + } + + if (body_dynamic_present) { + if (body_dynamic_size == 0) { + body_dynamic_size = 1; + } + } else { + body_dynamic_size = 0; + } + + req->out.size = SMB2_HDR_BODY+NBT_HDR_SIZE+body_fixed_size; + + req->out.allocated = req->out.size + body_dynamic_size; + req->out.buffer = talloc_array(req, uint8_t, + req->out.allocated); + NT_STATUS_HAVE_NO_MEMORY(req->out.buffer); + + req->out.hdr = req->out.buffer + NBT_HDR_SIZE; + req->out.body = req->out.hdr + SMB2_HDR_BODY; + req->out.body_fixed = body_fixed_size; + req->out.body_size = body_fixed_size; + req->out.dynamic = (body_dynamic_size ? req->out.body + body_fixed_size : NULL); + + SIVAL(req->out.hdr, 0, SMB2_MAGIC); + SSVAL(req->out.hdr, SMB2_HDR_LENGTH, SMB2_HDR_BODY); + SSVAL(req->out.hdr, SMB2_HDR_CREDIT_CHARGE, + SVAL(req->in.hdr, SMB2_HDR_CREDIT_CHARGE)); + SIVAL(req->out.hdr, SMB2_HDR_STATUS, NT_STATUS_V(req->status)); + SSVAL(req->out.hdr, SMB2_HDR_OPCODE, SVAL(req->in.hdr, SMB2_HDR_OPCODE)); + SSVAL(req->out.hdr, SMB2_HDR_CREDIT, credits); + SIVAL(req->out.hdr, SMB2_HDR_FLAGS, flags); + SIVAL(req->out.hdr, SMB2_HDR_NEXT_COMMAND, 0); + SBVAL(req->out.hdr, SMB2_HDR_MESSAGE_ID, req->seqnum); + SIVAL(req->out.hdr, SMB2_HDR_PID, pid); + SIVAL(req->out.hdr, SMB2_HDR_TID, tid); + SBVAL(req->out.hdr, SMB2_HDR_SESSION_ID, BVAL(req->in.hdr, SMB2_HDR_SESSION_ID)); + memcpy(req->out.hdr+SMB2_HDR_SIGNATURE, + req->in.hdr+SMB2_HDR_SIGNATURE, 16); + + /* set the length of the fixed body part and +1 if there's a dynamic part also */ + SSVAL(req->out.body, 0, body_fixed_size + (body_dynamic_size?1:0)); + + /* + * if we have a dynamic part, make sure the first byte + * which is always be part of the packet is initialized + */ + if (body_dynamic_size) { + req->out.size += 1; + SCVAL(req->out.dynamic, 0, 0); + } + + return NT_STATUS_OK; +} + +static NTSTATUS smb2srv_reply(struct smb2srv_request *req); + +static void smb2srv_chain_reply(struct smb2srv_request *p_req) +{ + NTSTATUS status; + struct smbsrv_connection *smb_conn = p_req->smb_conn; + struct smb2srv_request *req; + uint32_t chain_offset; + uint32_t protocol_version; + uint16_t buffer_code; + uint32_t dynamic_size; + uint32_t flags; + uint32_t last_hdr_offset; + + last_hdr_offset = p_req->in.hdr - p_req->in.buffer; + + chain_offset = p_req->chain_offset; + p_req->chain_offset = 0; + + if (p_req->in.size < (last_hdr_offset + chain_offset + SMB2_MIN_SIZE_NO_BODY)) { + DEBUG(2,("Invalid SMB2 chained packet at offset 0x%X from last hdr 0x%X\n", + chain_offset, last_hdr_offset)); + smbsrv_terminate_connection(smb_conn, "Invalid SMB2 chained packet"); + return; + } + + protocol_version = IVAL(p_req->in.buffer, last_hdr_offset + chain_offset); + if (protocol_version != SMB2_MAGIC) { + DEBUG(2,("Invalid SMB chained packet: protocol prefix: 0x%08X\n", + protocol_version)); + smbsrv_terminate_connection(smb_conn, "NON-SMB2 chained packet"); + return; + } + + req = smb2srv_init_request(smb_conn); + if (!req) { + smbsrv_terminate_connection(smb_conn, "SMB2 chained packet - no memory"); + return; + } + + talloc_steal(req, p_req); + + req->in.buffer = talloc_steal(req, p_req->in.buffer); + req->in.size = p_req->in.size; + req->request_time = p_req->request_time; + req->in.allocated = req->in.size; + + req->in.hdr = req->in.buffer+ last_hdr_offset + chain_offset; + req->in.body = req->in.hdr + SMB2_HDR_BODY; + req->in.body_size = req->in.size - (last_hdr_offset+ chain_offset + SMB2_HDR_BODY); + req->in.dynamic = NULL; + + req->seqnum = BVAL(req->in.hdr, SMB2_HDR_MESSAGE_ID); + + if (req->in.body_size < 2) { + /* error handling for this is different for negprot to + other packet types */ + uint16_t opcode = SVAL(req->in.hdr, SMB2_HDR_OPCODE); + if (opcode == SMB2_OP_NEGPROT) { + smbsrv_terminate_connection(smb_conn, "Bad body size in SMB2 negprot"); + return; + } else { + smb2srv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + } + + buffer_code = SVAL(req->in.body, 0); + req->in.body_fixed = (buffer_code & ~1); + dynamic_size = req->in.body_size - req->in.body_fixed; + + if (dynamic_size != 0 && (buffer_code & 1)) { + req->in.dynamic = req->in.body + req->in.body_fixed; + if (smb2_oob(&req->in, req->in.dynamic, dynamic_size)) { + DEBUG(1,("SMB2 chained request invalid dynamic size 0x%x\n", + dynamic_size)); + smb2srv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + } + + smb2srv_setup_bufinfo(req); + + flags = IVAL(req->in.hdr, SMB2_HDR_FLAGS); + if (flags & SMB2_HDR_FLAG_CHAINED) { + if (p_req->chained_file_handle) { + memcpy(req->_chained_file_handle, + p_req->_chained_file_handle, + sizeof(req->_chained_file_handle)); + req->chained_file_handle = req->_chained_file_handle; + } + req->chained_session_id = p_req->chained_session_id; + req->chained_tree_id = p_req->chained_tree_id; + req->chain_status = p_req->chain_status; + } + + /* + * TODO: - make sure the length field is 64 + * - make sure it's a request + */ + + status = smb2srv_reply(req); + if (!NT_STATUS_IS_OK(status)) { + smbsrv_terminate_connection(smb_conn, nt_errstr(status)); + return; + } +} + +void smb2srv_send_reply(struct smb2srv_request *req) +{ + DATA_BLOB blob; + NTSTATUS status; + + if (req->smb_conn->connection->event.fde == NULL) { + /* the socket has been destroyed - no point trying to send a reply! */ + talloc_free(req); + return; + } + + if (req->out.size > NBT_HDR_SIZE) { + _smb_setlen_tcp(req->out.buffer, req->out.size - NBT_HDR_SIZE); + } + + /* if signing is active on the session then sign the packet */ + if (req->is_signed) { + status = smb2_sign_message(&req->out, + req->session->session_info->session_key); + if (!NT_STATUS_IS_OK(status)) { + smbsrv_terminate_connection(req->smb_conn, nt_errstr(status)); + return; + } + } + + + blob = data_blob_const(req->out.buffer, req->out.size); + status = packet_send(req->smb_conn->packet, blob); + if (!NT_STATUS_IS_OK(status)) { + smbsrv_terminate_connection(req->smb_conn, nt_errstr(status)); + return; + } + if (req->chain_offset) { + smb2srv_chain_reply(req); + return; + } + talloc_free(req); +} + +void smb2srv_send_error(struct smb2srv_request *req, NTSTATUS error) +{ + NTSTATUS status; + + if (req->smb_conn->connection->event.fde == NULL) { + /* the socket has been destroyed - no point trying to send an error! */ + talloc_free(req); + return; + } + + status = smb2srv_setup_reply(req, 8, true, 0); + if (!NT_STATUS_IS_OK(status)) { + smbsrv_terminate_connection(req->smb_conn, nt_errstr(status)); + talloc_free(req); + return; + } + + SIVAL(req->out.hdr, SMB2_HDR_STATUS, NT_STATUS_V(error)); + + SSVAL(req->out.body, 0x02, 0); + SIVAL(req->out.body, 0x04, 0); + + req->chain_status = NT_STATUS_INVALID_PARAMETER; + + smb2srv_send_reply(req); +} + +static NTSTATUS smb2srv_reply(struct smb2srv_request *req) +{ + uint16_t opcode; + uint32_t tid; + uint64_t uid; + uint32_t flags; + + if (SVAL(req->in.hdr, SMB2_HDR_LENGTH) != SMB2_HDR_BODY) { + smbsrv_terminate_connection(req->smb_conn, "Invalid SMB2 header length"); + return NT_STATUS_INVALID_PARAMETER; + } + opcode = SVAL(req->in.hdr, SMB2_HDR_OPCODE); + req->chain_offset = IVAL(req->in.hdr, SMB2_HDR_NEXT_COMMAND); + req->seqnum = BVAL(req->in.hdr, SMB2_HDR_MESSAGE_ID); + tid = IVAL(req->in.hdr, SMB2_HDR_TID); + uid = BVAL(req->in.hdr, SMB2_HDR_SESSION_ID); + flags = IVAL(req->in.hdr, SMB2_HDR_FLAGS); + + if (opcode != SMB2_OP_CANCEL && + req->smb_conn->highest_smb2_seqnum != 0 && + req->seqnum <= req->smb_conn->highest_smb2_seqnum) { + smbsrv_terminate_connection(req->smb_conn, "Invalid SMB2 sequence number"); + return NT_STATUS_INVALID_PARAMETER; + } + if (opcode != SMB2_OP_CANCEL) { + req->smb_conn->highest_smb2_seqnum = req->seqnum; + } + + if (flags & SMB2_HDR_FLAG_CHAINED) { + uid = req->chained_session_id; + tid = req->chained_tree_id; + } + + req->session = smbsrv_session_find(req->smb_conn, uid, req->request_time); + req->tcon = smbsrv_smb2_tcon_find(req->session, tid, req->request_time); + + req->chained_session_id = uid; + req->chained_tree_id = tid; + + errno = 0; + + /* supporting signing is mandatory in SMB2, and is per-packet. So we + should check the signature on any incoming packet that is signed, and + should give a signed reply to any signed request */ + if (flags & SMB2_HDR_FLAG_SIGNED) { + NTSTATUS status; + + if (!req->session) goto nosession; + + req->is_signed = true; + status = smb2_check_signature(&req->in, + req->session->session_info->session_key); + if (!NT_STATUS_IS_OK(status)) { + smb2srv_send_error(req, status); + return NT_STATUS_OK; + } + } else if (req->session && req->session->smb2_signing.active) { + /* we require signing and this request was not signed */ + smb2srv_send_error(req, NT_STATUS_ACCESS_DENIED); + return NT_STATUS_OK; + } + + if (!NT_STATUS_IS_OK(req->chain_status)) { + smb2srv_send_error(req, req->chain_status); + return NT_STATUS_OK; + } + + switch (opcode) { + case SMB2_OP_NEGPROT: + smb2srv_negprot_recv(req); + return NT_STATUS_OK; + case SMB2_OP_SESSSETUP: + smb2srv_sesssetup_recv(req); + return NT_STATUS_OK; + case SMB2_OP_LOGOFF: + if (!req->session) goto nosession; + smb2srv_logoff_recv(req); + return NT_STATUS_OK; + case SMB2_OP_TCON: + if (!req->session) goto nosession; + smb2srv_tcon_recv(req); + return NT_STATUS_OK; + case SMB2_OP_TDIS: + if (!req->session) goto nosession; + if (!req->tcon) goto notcon; + smb2srv_tdis_recv(req); + return NT_STATUS_OK; + case SMB2_OP_CREATE: + if (!req->session) goto nosession; + if (!req->tcon) goto notcon; + smb2srv_create_recv(req); + return NT_STATUS_OK; + case SMB2_OP_CLOSE: + if (!req->session) goto nosession; + if (!req->tcon) goto notcon; + smb2srv_close_recv(req); + return NT_STATUS_OK; + case SMB2_OP_FLUSH: + if (!req->session) goto nosession; + if (!req->tcon) goto notcon; + smb2srv_flush_recv(req); + return NT_STATUS_OK; + case SMB2_OP_READ: + if (!req->session) goto nosession; + if (!req->tcon) goto notcon; + smb2srv_read_recv(req); + return NT_STATUS_OK; + case SMB2_OP_WRITE: + if (!req->session) goto nosession; + if (!req->tcon) goto notcon; + smb2srv_write_recv(req); + return NT_STATUS_OK; + case SMB2_OP_LOCK: + if (!req->session) goto nosession; + if (!req->tcon) goto notcon; + smb2srv_lock_recv(req); + return NT_STATUS_OK; + case SMB2_OP_IOCTL: + if (!req->session) goto nosession; + if (!req->tcon) goto notcon; + smb2srv_ioctl_recv(req); + return NT_STATUS_OK; + case SMB2_OP_CANCEL: + smb2srv_cancel_recv(req); + return NT_STATUS_OK; + case SMB2_OP_KEEPALIVE: + smb2srv_keepalive_recv(req); + return NT_STATUS_OK; + case SMB2_OP_QUERY_DIRECTORY: + if (!req->session) goto nosession; + if (!req->tcon) goto notcon; + smb2srv_find_recv(req); + return NT_STATUS_OK; + case SMB2_OP_NOTIFY: + if (!req->session) goto nosession; + if (!req->tcon) goto notcon; + smb2srv_notify_recv(req); + return NT_STATUS_OK; + case SMB2_OP_GETINFO: + if (!req->session) goto nosession; + if (!req->tcon) goto notcon; + smb2srv_getinfo_recv(req); + return NT_STATUS_OK; + case SMB2_OP_SETINFO: + if (!req->session) goto nosession; + if (!req->tcon) goto notcon; + smb2srv_setinfo_recv(req); + return NT_STATUS_OK; + case SMB2_OP_BREAK: + if (!req->session) goto nosession; + if (!req->tcon) goto notcon; + smb2srv_break_recv(req); + return NT_STATUS_OK; + } + + DEBUG(1,("Invalid SMB2 opcode: 0x%04X\n", opcode)); + smbsrv_terminate_connection(req->smb_conn, "Invalid SMB2 opcode"); + return NT_STATUS_OK; + +nosession: + smb2srv_send_error(req, NT_STATUS_USER_SESSION_DELETED); + return NT_STATUS_OK; +notcon: + smb2srv_send_error(req, NT_STATUS_NETWORK_NAME_DELETED); + return NT_STATUS_OK; +} + +NTSTATUS smbsrv_recv_smb2_request(void *private_data, DATA_BLOB blob) +{ + struct smbsrv_connection *smb_conn = talloc_get_type(private_data, struct smbsrv_connection); + struct smb2srv_request *req; + struct timeval cur_time = timeval_current(); + uint32_t protocol_version; + uint16_t buffer_code; + uint32_t dynamic_size; + uint32_t flags; + + smb_conn->statistics.last_request_time = cur_time; + + /* see if its a special NBT packet */ + if (CVAL(blob.data,0) != 0) { + DEBUG(2,("Special NBT packet on SMB2 connection")); + smbsrv_terminate_connection(smb_conn, "Special NBT packet on SMB2 connection"); + return NT_STATUS_OK; + } + + if (blob.length < (NBT_HDR_SIZE + SMB2_MIN_SIZE_NO_BODY)) { + DEBUG(2,("Invalid SMB2 packet length count %ld\n", (long)blob.length)); + smbsrv_terminate_connection(smb_conn, "Invalid SMB2 packet"); + return NT_STATUS_OK; + } + + protocol_version = IVAL(blob.data, NBT_HDR_SIZE); + if (protocol_version != SMB2_MAGIC) { + DEBUG(2,("Invalid SMB packet: protocol prefix: 0x%08X\n", + protocol_version)); + smbsrv_terminate_connection(smb_conn, "NON-SMB2 packet"); + return NT_STATUS_OK; + } + + req = smb2srv_init_request(smb_conn); + NT_STATUS_HAVE_NO_MEMORY(req); + + req->in.buffer = talloc_steal(req, blob.data); + req->in.size = blob.length; + req->request_time = cur_time; + req->in.allocated = req->in.size; + + req->in.hdr = req->in.buffer+ NBT_HDR_SIZE; + req->in.body = req->in.hdr + SMB2_HDR_BODY; + req->in.body_size = req->in.size - (SMB2_HDR_BODY+NBT_HDR_SIZE); + req->in.dynamic = NULL; + + req->seqnum = BVAL(req->in.hdr, SMB2_HDR_MESSAGE_ID); + + if (req->in.body_size < 2) { + /* error handling for this is different for negprot to + other packet types */ + uint16_t opcode = SVAL(req->in.hdr, SMB2_HDR_OPCODE); + if (opcode == SMB2_OP_NEGPROT) { + smbsrv_terminate_connection(req->smb_conn, "Bad body size in SMB2 negprot"); + return NT_STATUS_OK; + } else { + smb2srv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return NT_STATUS_OK; + } + } + + buffer_code = SVAL(req->in.body, 0); + req->in.body_fixed = (buffer_code & ~1); + dynamic_size = req->in.body_size - req->in.body_fixed; + + if (dynamic_size != 0 && (buffer_code & 1)) { + req->in.dynamic = req->in.body + req->in.body_fixed; + if (smb2_oob(&req->in, req->in.dynamic, dynamic_size)) { + DEBUG(1,("SMB2 request invalid dynamic size 0x%x\n", + dynamic_size)); + smb2srv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return NT_STATUS_OK; + } + } + + smb2srv_setup_bufinfo(req); + + /* + * TODO: - make sure the length field is 64 + * - make sure it's a request + */ + + flags = IVAL(req->in.hdr, SMB2_HDR_FLAGS); + /* the first request should never have the related flag set */ + if (flags & SMB2_HDR_FLAG_CHAINED) { + req->chain_status = NT_STATUS_INVALID_PARAMETER; + } + + return smb2srv_reply(req); +} + +static NTSTATUS smb2srv_init_pending(struct smbsrv_connection *smb_conn) +{ + smb_conn->requests2.idtree_req = idr_init(smb_conn); + NT_STATUS_HAVE_NO_MEMORY(smb_conn->requests2.idtree_req); + smb_conn->requests2.idtree_limit = 0x00FFFFFF & (UINT32_MAX - 1); + smb_conn->requests2.list = NULL; + + return NT_STATUS_OK; +} + +NTSTATUS smb2srv_queue_pending(struct smb2srv_request *req) +{ + NTSTATUS status; + bool signing_used = false; + int id; + uint16_t credits = SVAL(req->in.hdr, SMB2_HDR_CREDIT); + + if (credits == 0) { + credits = 1; + } + + if (req->pending_id) { + return NT_STATUS_INTERNAL_ERROR; + } + + if (req->smb_conn->connection->event.fde == NULL) { + /* the socket has been destroyed - no point trying to send an error! */ + return NT_STATUS_REMOTE_DISCONNECT; + } + + id = idr_get_new_above(req->smb_conn->requests2.idtree_req, req, + 1, req->smb_conn->requests2.idtree_limit); + if (id == -1) { + return NT_STATUS_INSUFFICIENT_RESOURCES; + } + + DLIST_ADD_END(req->smb_conn->requests2.list, req); + req->pending_id = id; + + talloc_set_destructor(req, smb2srv_request_deny_destructor); + + status = smb2srv_setup_reply(req, 8, true, 0); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + SIVAL(req->out.hdr, SMB2_HDR_STATUS, NT_STATUS_V(NT_STATUS_PENDING)); + SSVAL(req->out.hdr, SMB2_HDR_CREDIT, credits); + + SSVAL(req->out.body, 0x02, 0); + SIVAL(req->out.body, 0x04, 0); + + /* if the real reply will be signed set the signed flags, but don't sign */ + if (req->is_signed) { + SIVAL(req->out.hdr, SMB2_HDR_FLAGS, IVAL(req->out.hdr, SMB2_HDR_FLAGS) | SMB2_HDR_FLAG_SIGNED); + signing_used = req->is_signed; + req->is_signed = false; + } + + smb2srv_send_reply(req); + + req->is_signed = signing_used; + + talloc_set_destructor(req, smb2srv_request_destructor); + return NT_STATUS_OK; +} + +void smb2srv_cancel_recv(struct smb2srv_request *req) +{ + uint32_t pending_id; + uint32_t flags; + void *p; + struct smb2srv_request *r; + + if (!req->session) goto done; + + flags = IVAL(req->in.hdr, SMB2_HDR_FLAGS); + pending_id = IVAL(req->in.hdr, SMB2_HDR_PID); + + if (!(flags & SMB2_HDR_FLAG_ASYNC)) { + /* TODO: what to do here? */ + goto done; + } + + p = idr_find(req->smb_conn->requests2.idtree_req, pending_id); + if (!p) goto done; + + r = talloc_get_type(p, struct smb2srv_request); + if (!r) goto done; + + if (!r->ntvfs) goto done; + + ntvfs_cancel(r->ntvfs); + +done: + /* we never generate a reply for a SMB2 Cancel */ + talloc_free(req); +} + +/* + * init the SMB2 protocol related stuff + */ +NTSTATUS smbsrv_init_smb2_connection(struct smbsrv_connection *smb_conn) +{ + NTSTATUS status; + + /* now initialise a few default values associated with this smb socket */ + smb_conn->negotiate.max_send = 0xFFFF; + + /* this is the size that w2k uses, and it appears to be important for + good performance */ + smb_conn->negotiate.max_recv = lpcfg_max_xmit(smb_conn->lp_ctx); + + smb_conn->negotiate.zone_offset = get_time_zone(time(NULL)); + + smb_conn->config.nt_status_support = true; + + status = smbsrv_init_sessions(smb_conn, UINT64_MAX); + NT_STATUS_NOT_OK_RETURN(status); + + status = smb2srv_init_pending(smb_conn); + NT_STATUS_NOT_OK_RETURN(status); + + return NT_STATUS_OK; + +} diff --git a/source4/smb_server/smb2/sesssetup.c b/source4/smb_server/smb2/sesssetup.c new file mode 100644 index 0000000..a8c4560 --- /dev/null +++ b/source4/smb_server/smb2/sesssetup.c @@ -0,0 +1,326 @@ +/* + Unix SMB2 implementation. + + Copyright (C) Andrew Bartlett 2001-2005 + Copyright (C) Stefan Metzmacher 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include <tevent.h> +#include "auth/gensec/gensec.h" +#include "auth/auth.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "smb_server/smb_server.h" +#include "smb_server/smb2/smb2_server.h" +#include "samba/service_stream.h" +#include "lib/stream/packet.h" + +static void smb2srv_sesssetup_send(struct smb2srv_request *req, union smb_sesssetup *io) +{ + if (NT_STATUS_IS_OK(req->status)) { + /* nothing */ + } else if (NT_STATUS_EQUAL(req->status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + /* nothing */ + } else { + smb2srv_send_error(req, req->status); + return; + } + + SMB2SRV_CHECK(smb2srv_setup_reply(req, 0x08, true, io->smb2.out.secblob.length)); + + SBVAL(req->out.hdr, SMB2_HDR_SESSION_ID, io->smb2.out.uid); + + SSVAL(req->out.body, 0x02, io->smb2.out.session_flags); + SMB2SRV_CHECK(smb2_push_o16s16_blob(&req->out, 0x04, io->smb2.out.secblob)); + + smb2srv_send_reply(req); +} + +struct smb2srv_sesssetup_callback_ctx { + struct smb2srv_request *req; + union smb_sesssetup *io; + struct smbsrv_session *smb_sess; +}; + +static void smb2srv_sesssetup_callback(struct tevent_req *subreq) +{ + struct smb2srv_sesssetup_callback_ctx *ctx = tevent_req_callback_data(subreq, + struct smb2srv_sesssetup_callback_ctx); + struct smb2srv_request *req = ctx->req; + union smb_sesssetup *io = ctx->io; + struct smbsrv_session *smb_sess = ctx->smb_sess; + struct auth_session_info *session_info = NULL; + enum security_user_level user_level; + NTSTATUS status; + + packet_recv_enable(req->smb_conn->packet); + + status = gensec_update_recv(subreq, req, &io->smb2.out.secblob); + TALLOC_FREE(subreq); + if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + goto done; + } else if (!NT_STATUS_IS_OK(status)) { + goto failed; + } + + status = gensec_session_info(smb_sess->gensec_ctx, smb_sess, &session_info); + if (!NT_STATUS_IS_OK(status)) { + goto failed; + } + + /* Ensure this is marked as a 'real' vuid, not one + * simply valid for the session setup leg */ + status = smbsrv_session_sesssetup_finished(smb_sess, session_info); + if (!NT_STATUS_IS_OK(status)) { + goto failed; + } + req->session = smb_sess; + + user_level = security_session_user_level(smb_sess->session_info, NULL); + if (user_level >= SECURITY_USER) { + if (smb_sess->smb2_signing.required) { + /* activate smb2 signing on the session */ + smb_sess->smb2_signing.active = true; + } + /* we need to sign the session setup response */ + req->is_signed = true; + } + +done: + io->smb2.out.uid = smb_sess->vuid; +failed: + req->status = nt_status_squash(status); + smb2srv_sesssetup_send(req, io); + if (!NT_STATUS_IS_OK(status) && ! + NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + talloc_free(smb_sess); + } +} + +static void smb2srv_sesssetup_backend(struct smb2srv_request *req, union smb_sesssetup *io) +{ + NTSTATUS status; + struct smb2srv_sesssetup_callback_ctx *callback_ctx; + struct smbsrv_session *smb_sess = NULL; + uint64_t vuid; + struct tevent_req *subreq; + + io->smb2.out.session_flags = 0; + io->smb2.out.uid = 0; + io->smb2.out.secblob = data_blob(NULL, 0); + + vuid = BVAL(req->in.hdr, SMB2_HDR_SESSION_ID); + + /* + * only when we got '0' we should allocate a new session + */ + if (vuid == 0) { + struct gensec_security *gensec_ctx; + struct tsocket_address *remote_address, *local_address; + + status = samba_server_gensec_start(req, + req->smb_conn->connection->event.ctx, + req->smb_conn->connection->msg_ctx, + req->smb_conn->lp_ctx, + req->smb_conn->negotiate.server_credentials, + "cifs", + &gensec_ctx); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to start GENSEC server code: %s\n", nt_errstr(status))); + goto failed; + } + + gensec_want_feature(gensec_ctx, GENSEC_FEATURE_SESSION_KEY); + gensec_want_feature(gensec_ctx, GENSEC_FEATURE_SMB_TRANSPORT); + + remote_address = socket_get_remote_addr(req->smb_conn->connection->socket, + req); + if (!remote_address) { + status = NT_STATUS_INTERNAL_ERROR; + DBG_ERR("Failed to obtain remote address"); + goto failed; + } + + status = gensec_set_remote_address(gensec_ctx, + remote_address); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Failed to set remote address"); + goto failed; + } + + local_address = socket_get_local_addr(req->smb_conn->connection->socket, + req); + if (!local_address) { + status = NT_STATUS_INTERNAL_ERROR; + DBG_ERR("Failed to obtain local address"); + goto failed; + } + + status = gensec_set_local_address(gensec_ctx, + local_address); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Failed to set local address"); + goto failed; + } + + status = gensec_set_target_service_description(gensec_ctx, + "SMB2"); + + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Failed to set service description"); + goto failed; + } + + status = gensec_start_mech_by_oid(gensec_ctx, GENSEC_OID_SPNEGO); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to start GENSEC SPNEGO server code: %s\n", nt_errstr(status))); + goto failed; + } + + /* allocate a new session */ + smb_sess = smbsrv_session_new(req->smb_conn, req->smb_conn, gensec_ctx); + if (!smb_sess) { + status = NT_STATUS_INSUFFICIENT_RESOURCES; + goto failed; + } + status = smbsrv_smb2_init_tcons(smb_sess); + if (!NT_STATUS_IS_OK(status)) { + goto failed; + } + } else { + /* lookup an existing session */ + smb_sess = smbsrv_session_find_sesssetup(req->smb_conn, vuid); + } + + if (!smb_sess) { + status = NT_STATUS_USER_SESSION_DELETED; + goto failed; + } + + if (smb_sess->session_info) { + /* see WSPP test suite - test 11 */ + status = NT_STATUS_REQUEST_NOT_ACCEPTED; + goto failed; + } + + if (!smb_sess->gensec_ctx) { + status = NT_STATUS_INTERNAL_ERROR; + DEBUG(1, ("Internal ERROR: no gensec_ctx on session: %s\n", nt_errstr(status))); + goto failed; + } + + callback_ctx = talloc(req, struct smb2srv_sesssetup_callback_ctx); + if (!callback_ctx) goto nomem; + callback_ctx->req = req; + callback_ctx->io = io; + callback_ctx->smb_sess = smb_sess; + + subreq = gensec_update_send(callback_ctx, + req->smb_conn->connection->event.ctx, + smb_sess->gensec_ctx, + io->smb2.in.secblob); + if (!subreq) goto nomem; + tevent_req_set_callback(subreq, smb2srv_sesssetup_callback, callback_ctx); + + /* note that we ignore SMB2_NEGOTIATE_SIGNING_ENABLED from the client. + This is deliberate as windows does not set it even when it does + set SMB2_NEGOTIATE_SIGNING_REQUIRED */ + if (io->smb2.in.security_mode & SMB2_NEGOTIATE_SIGNING_REQUIRED) { + smb_sess->smb2_signing.required = true; + } + + /* disable receipt of more packets on this socket until we've + finished with the session setup. This avoids a problem with + crashes if we get EOF on the socket while processing a session + setup */ + packet_recv_disable(req->smb_conn->packet); + + return; +nomem: + status = NT_STATUS_NO_MEMORY; +failed: + talloc_free(smb_sess); + req->status = nt_status_squash(status); + smb2srv_sesssetup_send(req, io); +} + +void smb2srv_sesssetup_recv(struct smb2srv_request *req) +{ + union smb_sesssetup *io; + + SMB2SRV_CHECK_BODY_SIZE(req, 0x18, true); + SMB2SRV_TALLOC_IO_PTR(io, union smb_sesssetup); + + io->smb2.level = RAW_SESSSETUP_SMB2; + io->smb2.in.vc_number = CVAL(req->in.body, 0x02); + io->smb2.in.security_mode = CVAL(req->in.body, 0x03); + io->smb2.in.capabilities = IVAL(req->in.body, 0x04); + io->smb2.in.channel = IVAL(req->in.body, 0x08); + io->smb2.in.previous_sessionid = BVAL(req->in.body, 0x10); + SMB2SRV_CHECK(smb2_pull_o16s16_blob(&req->in, io, req->in.body+0x0C, &io->smb2.in.secblob)); + + smb2srv_sesssetup_backend(req, io); +} + +static int smb2srv_cleanup_session_destructor(struct smbsrv_session **session) +{ + /* TODO: call ntvfs backends to close file of this session */ + DEBUG(0,("free session[%p]\n", *session)); + talloc_free(*session); + return 0; +} + +static NTSTATUS smb2srv_logoff_backend(struct smb2srv_request *req) +{ + struct smbsrv_session **session_ptr; + + /* we need to destroy the session after sending the reply */ + session_ptr = talloc(req, struct smbsrv_session *); + NT_STATUS_HAVE_NO_MEMORY(session_ptr); + + *session_ptr = req->session; + talloc_set_destructor(session_ptr, smb2srv_cleanup_session_destructor); + + return NT_STATUS_OK; +} + +static void smb2srv_logoff_send(struct smb2srv_request *req) +{ + if (NT_STATUS_IS_ERR(req->status)) { + smb2srv_send_error(req, req->status); + return; + } + + SMB2SRV_CHECK(smb2srv_setup_reply(req, 0x04, false, 0)); + + SSVAL(req->out.body, 0x02, 0); + + smb2srv_send_reply(req); +} + +void smb2srv_logoff_recv(struct smb2srv_request *req) +{ + SMB2SRV_CHECK_BODY_SIZE(req, 0x04, false); + + req->status = smb2srv_logoff_backend(req); + + if (req->control_flags & SMB2SRV_REQ_CTRL_FLAG_NOT_REPLY) { + talloc_free(req); + return; + } + smb2srv_logoff_send(req); +} diff --git a/source4/smb_server/smb2/smb2_server.h b/source4/smb_server/smb2/smb2_server.h new file mode 100644 index 0000000..5fe12fe --- /dev/null +++ b/source4/smb_server/smb2/smb2_server.h @@ -0,0 +1,192 @@ +/* + Unix SMB2 implementation. + + Copyright (C) Stefan Metzmacher 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/>. +*/ + +/* the context for a single SMB2 request. This is passed to any request-context + functions */ +struct smb2srv_request { + /* the smbsrv_connection needs a list of requests queued for send */ + struct smb2srv_request *next, *prev; + + /* the server_context contains all context specific to this SMB socket */ + struct smbsrv_connection *smb_conn; + + /* conn is only set for operations that have a valid TID */ + struct smbsrv_tcon *tcon; + + /* the session context is derived from the vuid */ + struct smbsrv_session *session; + +#define SMB2SRV_REQ_CTRL_FLAG_NOT_REPLY (1<<0) + uint32_t control_flags; + + /* the system time when the request arrived */ + struct timeval request_time; + + /* a pointer to the per request union smb_* io structure */ + void *io_ptr; + + /* the ntvfs_request */ + struct ntvfs_request *ntvfs; + + /* Now the SMB2 specific stuff */ + + /* the status the backend returned */ + NTSTATUS status; + + /* for matching request and reply */ + uint64_t seqnum; + + /* the id that can be used to cancel the request */ + uint32_t pending_id; + + /* the offset to the next SMB2 Header for chained requests */ + uint32_t chain_offset; + + /* the status we return for following chained requests */ + NTSTATUS chain_status; + + /* chained file handle */ + uint8_t _chained_file_handle[16]; + uint8_t *chained_file_handle; + uint64_t chained_session_id; + uint32_t chained_tree_id; + + bool is_signed; + + struct smb2_request_buffer in; + struct smb2_request_buffer out; +}; + +struct smbsrv_request; + +#include "smb_server/smb2/smb2_proto.h" + +/* useful way of catching field size errors with file and line number */ +#define SMB2SRV_CHECK_BODY_SIZE(req, size, dynamic) do { \ + size_t is_size = req->in.body_size; \ + uint16_t field_size; \ + uint16_t want_size = ((dynamic)?(size)+1:(size)); \ + if (is_size < (size)) { \ + DEBUG(0,("%s: buffer too small 0x%x. Expected 0x%x\n", \ + __location__, (unsigned)is_size, (unsigned)want_size)); \ + smb2srv_send_error(req, NT_STATUS_INVALID_PARAMETER); \ + return; \ + }\ + field_size = SVAL(req->in.body, 0); \ + if (field_size != want_size) { \ + DEBUG(0,("%s: unexpected fixed body size 0x%x. Expected 0x%x\n", \ + __location__, (unsigned)field_size, (unsigned)want_size)); \ + smb2srv_send_error(req, NT_STATUS_INVALID_PARAMETER); \ + return; \ + } \ +} while (0) + +#define SMB2SRV_CHECK(cmd) do {\ + NTSTATUS _status; \ + _status = cmd; \ + if (!NT_STATUS_IS_OK(_status)) { \ + smb2srv_send_error(req, _status); \ + return; \ + } \ +} while (0) + +/* useful wrapper for talloc with NO_MEMORY reply */ +#define SMB2SRV_TALLOC_IO_PTR(ptr, type) do { \ + ptr = talloc(req, type); \ + if (!ptr) { \ + smb2srv_send_error(req, NT_STATUS_NO_MEMORY); \ + return; \ + } \ + req->io_ptr = ptr; \ +} while (0) + +#define SMB2SRV_SETUP_NTVFS_REQUEST(send_fn, state) do { \ + req->ntvfs = ntvfs_request_create(req->tcon->ntvfs, req, \ + req->session->session_info,\ + 0, \ + req->request_time, \ + req, send_fn, state); \ + if (!req->ntvfs) { \ + smb2srv_send_error(req, NT_STATUS_NO_MEMORY); \ + return; \ + } \ + (void)talloc_steal(req->tcon->ntvfs, req); \ + req->ntvfs->frontend_data.private_data = req; \ +} while (0) + +#define SMB2SRV_CHECK_FILE_HANDLE(handle) do { \ + if (!handle) { \ + smb2srv_send_error(req, NT_STATUS_FILE_CLOSED); \ + return; \ + } \ +} while (0) + +/* + check if the backend wants to handle the request asynchronously. + if it wants it handled synchronously then call the send function + immediately +*/ +#define SMB2SRV_CALL_NTVFS_BACKEND(cmd) do { \ + req->ntvfs->async_states->status = cmd; \ + if (req->ntvfs->async_states->state & NTVFS_ASYNC_STATE_ASYNC) { \ + NTSTATUS _status; \ + _status = smb2srv_queue_pending(req); \ + if (!NT_STATUS_IS_OK(_status)) { \ + ntvfs_cancel(req->ntvfs); \ + } \ + } else { \ + req->ntvfs->async_states->send_fn(req->ntvfs); \ + } \ +} while (0) + +/* check req->ntvfs->async_states->status and if not OK then send an error reply */ +#define SMB2SRV_CHECK_ASYNC_STATUS_ERR_SIMPLE do { \ + req = talloc_get_type(ntvfs->async_states->private_data, struct smb2srv_request); \ + if (ntvfs->async_states->state & NTVFS_ASYNC_STATE_CLOSE || NT_STATUS_EQUAL(ntvfs->async_states->status, NT_STATUS_NET_WRITE_FAULT)) { \ + smbsrv_terminate_connection(req->smb_conn, get_friendly_nt_error_msg (ntvfs->async_states->status)); \ + talloc_free(req); \ + return; \ + } \ + req->status = ntvfs->async_states->status; \ + if (NT_STATUS_IS_ERR(ntvfs->async_states->status)) { \ + smb2srv_send_error(req, ntvfs->async_states->status); \ + return; \ + } \ +} while (0) +#define SMB2SRV_CHECK_ASYNC_STATUS_ERR(ptr, type) do { \ + SMB2SRV_CHECK_ASYNC_STATUS_ERR_SIMPLE; \ + ptr = talloc_get_type(req->io_ptr, type); \ +} while (0) +#define SMB2SRV_CHECK_ASYNC_STATUS_SIMPLE do { \ + req = talloc_get_type(ntvfs->async_states->private_data, struct smb2srv_request); \ + if (ntvfs->async_states->state & NTVFS_ASYNC_STATE_CLOSE || NT_STATUS_EQUAL(ntvfs->async_states->status, NT_STATUS_NET_WRITE_FAULT)) { \ + smbsrv_terminate_connection(req->smb_conn, get_friendly_nt_error_msg (ntvfs->async_states->status)); \ + talloc_free(req); \ + return; \ + } \ + req->status = ntvfs->async_states->status; \ + if (!NT_STATUS_IS_OK(ntvfs->async_states->status)) { \ + smb2srv_send_error(req, ntvfs->async_states->status); \ + return; \ + } \ +} while (0) +#define SMB2SRV_CHECK_ASYNC_STATUS(ptr, type) do { \ + SMB2SRV_CHECK_ASYNC_STATUS_SIMPLE; \ + ptr = talloc_get_type(req->io_ptr, type); \ +} while (0) diff --git a/source4/smb_server/smb2/tcon.c b/source4/smb_server/smb2/tcon.c new file mode 100644 index 0000000..0c56420 --- /dev/null +++ b/source4/smb_server/smb2/tcon.c @@ -0,0 +1,446 @@ +/* + Unix SMB2 implementation. + + Copyright (C) Stefan Metzmacher 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "smb_server/smb_server.h" +#include "smb_server/smb2/smb2_server.h" +#include "samba/service_stream.h" +#include "ntvfs/ntvfs.h" + +/* + send an oplock break request to a client +*/ +static NTSTATUS smb2srv_send_oplock_break(void *p, struct ntvfs_handle *h, uint8_t level) +{ + struct smbsrv_handle *handle = talloc_get_type(h->frontend_data.private_data, + struct smbsrv_handle); + struct smb2srv_request *req; + NTSTATUS status; + + /* setup a dummy request structure */ + req = smb2srv_init_request(handle->tcon->smb_conn); + NT_STATUS_HAVE_NO_MEMORY(req); + + req->in.buffer = talloc_array(req, uint8_t, + NBT_HDR_SIZE + SMB2_MIN_SIZE); + NT_STATUS_HAVE_NO_MEMORY(req->in.buffer); + req->in.size = NBT_HDR_SIZE + SMB2_MIN_SIZE; + req->in.allocated = req->in.size; + + req->in.hdr = req->in.buffer+ NBT_HDR_SIZE; + req->in.body = req->in.hdr + SMB2_HDR_BODY; + req->in.body_size = req->in.size - (SMB2_HDR_BODY+NBT_HDR_SIZE); + req->in.dynamic = NULL; + + req->seqnum = UINT64_MAX; + + smb2srv_setup_bufinfo(req); + + SIVAL(req->in.hdr, 0, SMB2_MAGIC); + SSVAL(req->in.hdr, SMB2_HDR_LENGTH, SMB2_HDR_BODY); + SSVAL(req->in.hdr, SMB2_HDR_EPOCH, 0); + SIVAL(req->in.hdr, SMB2_HDR_STATUS, 0); + SSVAL(req->in.hdr, SMB2_HDR_OPCODE, SMB2_OP_BREAK); + SSVAL(req->in.hdr, SMB2_HDR_CREDIT, 0); + SIVAL(req->in.hdr, SMB2_HDR_FLAGS, 0); + SIVAL(req->in.hdr, SMB2_HDR_NEXT_COMMAND, 0); + SBVAL(req->in.hdr, SMB2_HDR_MESSAGE_ID, 0); + SIVAL(req->in.hdr, SMB2_HDR_PID, 0); + SIVAL(req->in.hdr, SMB2_HDR_TID, 0); + SBVAL(req->in.hdr, SMB2_HDR_SESSION_ID, 0); + memset(req->in.hdr+SMB2_HDR_SIGNATURE, 0, 16); + + SSVAL(req->in.body, 0, 2); + + status = smb2srv_setup_reply(req, 0x18, false, 0); + NT_STATUS_NOT_OK_RETURN(status); + + SSVAL(req->out.hdr, SMB2_HDR_CREDIT, 0x0000); + + SSVAL(req->out.body, 0x02, 0x0001); + SIVAL(req->out.body, 0x04, 0x00000000); + smb2srv_push_handle(req->out.body, 0x08, h); + + smb2srv_send_reply(req); + + return NT_STATUS_OK; +} + +struct ntvfs_handle *smb2srv_pull_handle(struct smb2srv_request *req, const uint8_t *base, unsigned int offset) +{ + struct smbsrv_tcon *tcon; + struct smbsrv_handle *handle; + uint32_t hid; + uint32_t tid; + uint64_t uid; + + /* + * if there're chained requests used the cached handle + * + * TODO: check if this also correct when the given handle + * isn't all 0xFF. + */ + if (req->chained_file_handle) { + base = req->chained_file_handle; + offset = 0; + } + + hid = IVAL(base, offset); + tid = IVAL(base, offset + 4); + uid = BVAL(base, offset + 8); + + /* if it's the wildcard handle, don't waste time to search it... */ + if (hid == UINT32_MAX && tid == UINT32_MAX && uid == UINT64_MAX) { + return NULL; + } + + /* + * if the (v)uid part doesn't match the given session the handle isn't + * valid + */ + if (uid != req->session->vuid) { + return NULL; + } + + /* + * the handle can belong to a different tcon + * as that TID in the SMB2 header says, but + * the request should succeed nevertheless! + * + * because of this we put the 32 bit TID into the + * 128 bit handle, so that we can extract the tcon from the + * handle + */ + tcon = req->tcon; + if (tid != req->tcon->tid) { + tcon = smbsrv_smb2_tcon_find(req->session, tid, req->request_time); + if (!tcon) { + return NULL; + } + } + + handle = smbsrv_smb2_handle_find(tcon, hid, req->request_time); + if (!handle) { + return NULL; + } + + /* + * as the smb2srv_tcon is a child object of the smb2srv_session + * the handle belongs to the correct session! + * + * Note: no check is needed here for SMB2 + */ + + /* + * as the handle may have overwritten the tcon + * we need to set it on the request so that the + * correct ntvfs context will be used for the ntvfs_*() request + * + * TODO: check if that's correct for chained requests as well! + */ + req->tcon = tcon; + return handle->ntvfs; +} + +void smb2srv_push_handle(uint8_t *base, unsigned int offset, struct ntvfs_handle *ntvfs) +{ + struct smbsrv_handle *handle = talloc_get_type(ntvfs->frontend_data.private_data, + struct smbsrv_handle); + + /* + * the handle is 128 bit on the wire + */ + SIVAL(base, offset, handle->hid); + SIVAL(base, offset + 4, handle->tcon->tid); + SBVAL(base, offset + 8, handle->session->vuid); +} + +static NTSTATUS smb2srv_handle_create_new(void *private_data, struct ntvfs_request *ntvfs, struct ntvfs_handle **_h) +{ + struct smb2srv_request *req = talloc_get_type(ntvfs->frontend_data.private_data, + struct smb2srv_request); + struct smbsrv_handle *handle; + struct ntvfs_handle *h; + + handle = smbsrv_handle_new(req->session, req->tcon, req, req->request_time); + if (!handle) return NT_STATUS_INSUFFICIENT_RESOURCES; + + h = talloc_zero(handle, struct ntvfs_handle); + if (!h) goto nomem; + + /* + * note: we don't set handle->ntvfs yet, + * this will be done by smbsrv_handle_make_valid() + * this makes sure the handle is invalid for clients + * until the ntvfs subsystem has made it valid + */ + h->ctx = ntvfs->ctx; + h->session_info = ntvfs->session_info; + h->smbpid = ntvfs->smbpid; + + h->frontend_data.private_data = handle; + + *_h = h; + return NT_STATUS_OK; +nomem: + talloc_free(handle); + return NT_STATUS_NO_MEMORY; +} + +static NTSTATUS smb2srv_handle_make_valid(void *private_data, struct ntvfs_handle *h) +{ + struct smbsrv_tcon *tcon = talloc_get_type(private_data, struct smbsrv_tcon); + struct smbsrv_handle *handle = talloc_get_type(h->frontend_data.private_data, + struct smbsrv_handle); + /* this tells the frontend that the handle is valid */ + handle->ntvfs = h; + /* this moves the smbsrv_request to the smbsrv_tcon memory context */ + talloc_steal(tcon, handle); + return NT_STATUS_OK; +} + +static void smb2srv_handle_destroy(void *private_data, struct ntvfs_handle *h) +{ + struct smbsrv_handle *handle = talloc_get_type(h->frontend_data.private_data, + struct smbsrv_handle); + talloc_free(handle); +} + +static struct ntvfs_handle *smb2srv_handle_search_by_wire_key(void *private_data, struct ntvfs_request *ntvfs, const DATA_BLOB *key) +{ + return NULL; +} + +static DATA_BLOB smb2srv_handle_get_wire_key(void *private_data, struct ntvfs_handle *handle, TALLOC_CTX *mem_ctx) +{ + return data_blob(NULL, 0); +} + +static NTSTATUS smb2srv_tcon_backend(struct smb2srv_request *req, union smb_tcon *io) +{ + struct smbsrv_tcon *tcon; + NTSTATUS status; + enum ntvfs_type type; + const char *service = io->smb2.in.path; + struct share_config *scfg; + char *sharetype; + uint64_t ntvfs_caps = 0; + + if (strncmp(service, "\\\\", 2) == 0) { + const char *p = strchr(service+2, '\\'); + if (p) { + service = p + 1; + } + } + + status = share_get_config(req, req->smb_conn->share_context, service, &scfg); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("smb2srv_tcon_backend: couldn't find service %s\n", service)); + return NT_STATUS_BAD_NETWORK_NAME; + } + + if (!socket_check_access(req->smb_conn->connection->socket, + scfg->name, + share_string_list_option(req, scfg, SHARE_HOSTS_ALLOW), + share_string_list_option(req, scfg, SHARE_HOSTS_DENY))) { + return NT_STATUS_ACCESS_DENIED; + } + + /* work out what sort of connection this is */ + sharetype = share_string_option(req, scfg, SHARE_TYPE, "DISK"); + if (sharetype && strcmp(sharetype, "IPC") == 0) { + type = NTVFS_IPC; + } else if (sharetype && strcmp(sharetype, "PRINTER") == 0) { + type = NTVFS_PRINT; + } else { + type = NTVFS_DISK; + } + TALLOC_FREE(sharetype); + + tcon = smbsrv_smb2_tcon_new(req->session, scfg->name); + if (!tcon) { + DEBUG(0,("smb2srv_tcon_backend: Couldn't find free connection.\n")); + return NT_STATUS_INSUFFICIENT_RESOURCES; + } + req->tcon = tcon; + + ntvfs_caps = NTVFS_CLIENT_CAP_LEVEL_II_OPLOCKS; + + /* init ntvfs function pointers */ + status = ntvfs_init_connection(tcon, scfg, type, + req->smb_conn->negotiate.protocol, + ntvfs_caps, + req->smb_conn->connection->event.ctx, + req->smb_conn->connection->msg_ctx, + req->smb_conn->lp_ctx, + req->smb_conn->connection->server_id, + &tcon->ntvfs); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("smb2srv_tcon_backend: ntvfs_init_connection failed for service %s\n", + scfg->name)); + goto failed; + } + + status = ntvfs_set_oplock_handler(tcon->ntvfs, smb2srv_send_oplock_break, tcon); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("smb2srv_tcon_backend: NTVFS failed to set the oplock handler!\n")); + goto failed; + } + + status = ntvfs_set_addresses(tcon->ntvfs, + req->smb_conn->connection->local_address, + req->smb_conn->connection->remote_address); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("smb2srv_tcon_backend: NTVFS failed to set the address!\n")); + goto failed; + } + + status = ntvfs_set_handle_callbacks(tcon->ntvfs, + smb2srv_handle_create_new, + smb2srv_handle_make_valid, + smb2srv_handle_destroy, + smb2srv_handle_search_by_wire_key, + smb2srv_handle_get_wire_key, + tcon); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("smb2srv_tcon_backend: NTVFS failed to set the handle callbacks!\n")); + goto failed; + } + + req->ntvfs = ntvfs_request_create(req->tcon->ntvfs, req, + req->session->session_info, + SVAL(req->in.hdr, SMB2_HDR_PID), + req->request_time, + req, NULL, 0); + if (!req->ntvfs) { + status = NT_STATUS_NO_MEMORY; + goto failed; + } + + io->smb2.out.share_type = (unsigned)type; /* 1 - DISK, 2 - Print, 3 - IPC */ + io->smb2.out.reserved = 0; + io->smb2.out.flags = 0x00000000; + io->smb2.out.capabilities = 0; + io->smb2.out.access_mask = SEC_RIGHTS_FILE_ALL; + + io->smb2.out.tid = tcon->tid; + + /* Invoke NTVFS connection hook */ + status = ntvfs_connect(req->ntvfs, io); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("smb2srv_tcon_backend: NTVFS ntvfs_connect() failed: %s!\n", nt_errstr(status))); + goto failed; + } + + return NT_STATUS_OK; + +failed: + req->tcon = NULL; + talloc_free(tcon); + return status; +} + +static void smb2srv_tcon_send(struct smb2srv_request *req, union smb_tcon *io) +{ + if (!NT_STATUS_IS_OK(req->status)) { + smb2srv_send_error(req, req->status); + return; + } + + SMB2SRV_CHECK(smb2srv_setup_reply(req, 0x10, false, 0)); + + SIVAL(req->out.hdr, SMB2_HDR_TID, io->smb2.out.tid); + + SCVAL(req->out.body, 0x02, io->smb2.out.share_type); + SCVAL(req->out.body, 0x03, io->smb2.out.reserved); + SIVAL(req->out.body, 0x04, io->smb2.out.flags); + SIVAL(req->out.body, 0x08, io->smb2.out.capabilities); + SIVAL(req->out.body, 0x0C, io->smb2.out.access_mask); + + smb2srv_send_reply(req); +} + +void smb2srv_tcon_recv(struct smb2srv_request *req) +{ + union smb_tcon *io; + + SMB2SRV_CHECK_BODY_SIZE(req, 0x08, true); + SMB2SRV_TALLOC_IO_PTR(io, union smb_tcon); + + io->smb2.level = RAW_TCON_SMB2; + io->smb2.in.reserved = SVAL(req->in.body, 0x02); + SMB2SRV_CHECK(smb2_pull_o16s16_string(&req->in, io, req->in.body+0x04, &io->smb2.in.path)); + + /* the VFS backend does not yet handle NULL paths */ + if (io->smb2.in.path == NULL) { + io->smb2.in.path = ""; + } + + req->status = smb2srv_tcon_backend(req, io); + + if (req->control_flags & SMB2SRV_REQ_CTRL_FLAG_NOT_REPLY) { + talloc_free(req); + return; + } + smb2srv_tcon_send(req, io); +} + +static NTSTATUS smb2srv_tdis_backend(struct smb2srv_request *req) +{ + /* TODO: call ntvfs backends to close file of this tcon */ + talloc_free(req->tcon); + req->tcon = NULL; + return NT_STATUS_OK; +} + +static void smb2srv_tdis_send(struct smb2srv_request *req) +{ + NTSTATUS status; + + if (NT_STATUS_IS_ERR(req->status)) { + smb2srv_send_error(req, req->status); + return; + } + + status = smb2srv_setup_reply(req, 0x04, false, 0); + if (!NT_STATUS_IS_OK(status)) { + smbsrv_terminate_connection(req->smb_conn, nt_errstr(status)); + talloc_free(req); + return; + } + + SSVAL(req->out.body, 0x02, 0); + + smb2srv_send_reply(req); +} + +void smb2srv_tdis_recv(struct smb2srv_request *req) +{ + SMB2SRV_CHECK_BODY_SIZE(req, 0x04, false); + + req->status = smb2srv_tdis_backend(req); + + if (req->control_flags & SMB2SRV_REQ_CTRL_FLAG_NOT_REPLY) { + talloc_free(req); + return; + } + smb2srv_tdis_send(req); +} diff --git a/source4/smb_server/smb2/wscript_build b/source4/smb_server/smb2/wscript_build new file mode 100644 index 0000000..7866ee9 --- /dev/null +++ b/source4/smb_server/smb2/wscript_build @@ -0,0 +1,9 @@ +#!/usr/bin/env python + +bld.SAMBA_SUBSYSTEM('SMB2_PROTOCOL', + source='receive.c negprot.c sesssetup.c tcon.c fileio.c fileinfo.c find.c keepalive.c', + autoproto='smb2_proto.h', + public_deps='ntvfs LIBPACKET LIBCLI_SMB2 samba_server_gensec NDR_DFSBLOBS', + enabled=bld.CONFIG_SET('WITH_NTVFS_FILESERVER') + ) + |