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 | |
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')
31 files changed, 13620 insertions, 0 deletions
diff --git a/source4/smb_server/blob.c b/source4/smb_server/blob.c new file mode 100644 index 0000000..2489329 --- /dev/null +++ b/source4/smb_server/blob.c @@ -0,0 +1,810 @@ +/* + Unix SMB/CIFS implementation. + + 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/>. +*/ + +#include "includes.h" +#include "smb_server/smb_server.h" +#include "librpc/gen_ndr/ndr_misc.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" + +#define BLOB_CHECK(cmd) do { \ + NTSTATUS _status; \ + _status = cmd; \ + NT_STATUS_NOT_OK_RETURN(_status); \ +} while (0) + +#define BLOB_CHECK_MIN_SIZE(blob, size) do { \ + if ((blob)->length < (size)) { \ + return NT_STATUS_INVALID_PARAMETER; \ + } \ +} while (0) + + +/* align the end of the blob on an 8 byte boundary */ +#define BLOB_ALIGN(blob, alignment) do { \ + if ((blob)->length & ((alignment)-1)) { \ + uint8_t _pad = (alignment) - ((blob)->length & ((alignment)-1)); \ + BLOB_CHECK(smbsrv_blob_fill_data(blob, blob, (blob)->length+_pad)); \ + } \ +} while (0) + +/* grow the data size of a trans2 reply */ +NTSTATUS smbsrv_blob_grow_data(TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, + uint32_t new_size) +{ + if (new_size > blob->length) { + uint8_t *p; + p = talloc_realloc(mem_ctx, blob->data, uint8_t, new_size); + NT_STATUS_HAVE_NO_MEMORY(p); + blob->data = p; + } + blob->length = new_size; + return NT_STATUS_OK; +} + +/* grow the data, zero filling any new bytes */ +NTSTATUS smbsrv_blob_fill_data(TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, + uint32_t new_size) +{ + uint32_t old_size = blob->length; + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, new_size)); + if (new_size > old_size) { + memset(blob->data + old_size, 0, new_size - old_size); + } + return NT_STATUS_OK; +} + +/* + pull a string from a blob in a trans2 request +*/ +size_t smbsrv_blob_pull_string(struct request_bufinfo *bufinfo, + const DATA_BLOB *blob, + uint16_t offset, + const char **str, + int flags) +{ + *str = NULL; + /* we use STR_NO_RANGE_CHECK because the params are allocated + separately in a DATA_BLOB, so we need to do our own range + checking */ + if (offset >= blob->length) { + return 0; + } + + return req_pull_string(bufinfo, str, + blob->data + offset, + blob->length - offset, + STR_NO_RANGE_CHECK | flags); +} + +/* + push a string into the data section of a trans2 request + return the number of bytes consumed in the output +*/ +static ssize_t smbsrv_blob_push_string(TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, + uint32_t len_offset, + uint32_t offset, + const char *str, + int dest_len, + int default_flags, + int flags) +{ + int alignment = 0, ret = 0, pkt_len; + + /* we use STR_NO_RANGE_CHECK because the params are allocated + separately in a DATA_BLOB, so we need to do our own range + checking */ + if (!str || offset >= blob->length) { + if (flags & STR_LEN8BIT) { + SCVAL(blob->data, len_offset, 0); + } else { + SIVAL(blob->data, len_offset, 0); + } + return 0; + } + + flags |= STR_NO_RANGE_CHECK; + + if (dest_len == -1 || (dest_len > blob->length - offset)) { + dest_len = blob->length - offset; + } + + if (!(flags & (STR_ASCII|STR_UNICODE))) { + flags |= default_flags; + } + + if ((offset&1) && (flags & STR_UNICODE) && !(flags & STR_NOALIGN)) { + alignment = 1; + if (dest_len > 0) { + SCVAL(blob->data + offset, 0, 0); + ret = push_string(blob->data + offset + 1, str, dest_len-1, flags); + } + } else { + ret = push_string(blob->data + offset, str, dest_len, flags); + } + if (ret == -1) { + return -1; + } + + /* sometimes the string needs to be terminated, but the length + on the wire must not include the termination! */ + pkt_len = ret; + + if ((flags & STR_LEN_NOTERM) && (flags & STR_TERMINATE)) { + if ((flags & STR_UNICODE) && ret >= 2) { + pkt_len = ret-2; + } + if ((flags & STR_ASCII) && ret >= 1) { + pkt_len = ret-1; + } + } + + if (flags & STR_LEN8BIT) { + SCVAL(blob->data, len_offset, pkt_len); + } else { + SIVAL(blob->data, len_offset, pkt_len); + } + + return ret + alignment; +} + +/* + append a string to the data section of a trans2 reply + len_offset points to the place in the packet where the length field + should go +*/ +NTSTATUS smbsrv_blob_append_string(TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, + const char *str, + unsigned int len_offset, + int default_flags, + int flags) +{ + ssize_t ret; + uint32_t offset; + const int max_bytes_per_char = 3; + + offset = blob->length; + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, offset + (2+strlen_m(str))*max_bytes_per_char)); + ret = smbsrv_blob_push_string(mem_ctx, blob, len_offset, offset, str, -1, default_flags, flags); + if (ret < 0) { + return NT_STATUS_FOOBAR; + } + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, offset + ret)); + return NT_STATUS_OK; +} + +NTSTATUS smbsrv_push_passthru_fsinfo(TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, + enum smb_fsinfo_level level, + union smb_fsinfo *fsinfo, + int default_str_flags) +{ + unsigned int i; + DATA_BLOB guid_blob; + + switch (level) { + case RAW_QFS_VOLUME_INFORMATION: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 18)); + + push_nttime(blob->data, 0, fsinfo->volume_info.out.create_time); + SIVAL(blob->data, 8, fsinfo->volume_info.out.serial_number); + SSVAL(blob->data, 16, 0); /* padding */ + BLOB_CHECK(smbsrv_blob_append_string(mem_ctx, blob, + fsinfo->volume_info.out.volume_name.s, + 12, default_str_flags, + STR_UNICODE)); + + return NT_STATUS_OK; + + case RAW_QFS_SIZE_INFORMATION: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 24)); + + SBVAL(blob->data, 0, fsinfo->size_info.out.total_alloc_units); + SBVAL(blob->data, 8, fsinfo->size_info.out.avail_alloc_units); + SIVAL(blob->data, 16, fsinfo->size_info.out.sectors_per_unit); + SIVAL(blob->data, 20, fsinfo->size_info.out.bytes_per_sector); + + return NT_STATUS_OK; + + case RAW_QFS_DEVICE_INFORMATION: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 8)); + + SIVAL(blob->data, 0, fsinfo->device_info.out.device_type); + SIVAL(blob->data, 4, fsinfo->device_info.out.characteristics); + + return NT_STATUS_OK; + + case RAW_QFS_ATTRIBUTE_INFORMATION: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 12)); + + SIVAL(blob->data, 0, fsinfo->attribute_info.out.fs_attr); + SIVAL(blob->data, 4, fsinfo->attribute_info.out.max_file_component_length); + /* this must not be null terminated or win98 gets + confused! also note that w2k3 returns this as + unicode even when ascii is negotiated */ + BLOB_CHECK(smbsrv_blob_append_string(mem_ctx, blob, + fsinfo->attribute_info.out.fs_type.s, + 8, default_str_flags, + STR_UNICODE)); + return NT_STATUS_OK; + + + case RAW_QFS_QUOTA_INFORMATION: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 48)); + + SBVAL(blob->data, 0, fsinfo->quota_information.out.unknown[0]); + SBVAL(blob->data, 8, fsinfo->quota_information.out.unknown[1]); + SBVAL(blob->data, 16, fsinfo->quota_information.out.unknown[2]); + SBVAL(blob->data, 24, fsinfo->quota_information.out.quota_soft); + SBVAL(blob->data, 32, fsinfo->quota_information.out.quota_hard); + SBVAL(blob->data, 40, fsinfo->quota_information.out.quota_flags); + + return NT_STATUS_OK; + + + case RAW_QFS_FULL_SIZE_INFORMATION: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 32)); + + SBVAL(blob->data, 0, fsinfo->full_size_information.out.total_alloc_units); + SBVAL(blob->data, 8, fsinfo->full_size_information.out.call_avail_alloc_units); + SBVAL(blob->data, 16, fsinfo->full_size_information.out.actual_avail_alloc_units); + SIVAL(blob->data, 24, fsinfo->full_size_information.out.sectors_per_unit); + SIVAL(blob->data, 28, fsinfo->full_size_information.out.bytes_per_sector); + + return NT_STATUS_OK; + + case RAW_QFS_OBJECTID_INFORMATION: { + NTSTATUS status; + + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 64)); + + status = GUID_to_ndr_blob(&fsinfo->objectid_information.out.guid, mem_ctx, &guid_blob); + if (!NT_STATUS_IS_OK(status)) { + BLOB_CHECK(status); + } + + memcpy(blob->data, guid_blob.data, guid_blob.length); + + for (i=0;i<6;i++) { + SBVAL(blob->data, 16 + 8*i, fsinfo->objectid_information.out.unknown[i]); + } + + return NT_STATUS_OK; + } + + case RAW_QFS_SECTOR_SIZE_INFORMATION: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 28)); + SIVAL(blob->data, 0, + fsinfo->sector_size_info.out.logical_bytes_per_sector); + SIVAL(blob->data, 4, + fsinfo->sector_size_info.out.phys_bytes_per_sector_atomic); + SIVAL(blob->data, 8, + fsinfo->sector_size_info.out.phys_bytes_per_sector_perf); + SIVAL(blob->data, 12, + fsinfo->sector_size_info.out.fs_effective_phys_bytes_per_sector_atomic); + SIVAL(blob->data, 16, + fsinfo->sector_size_info.out.flags); + SIVAL(blob->data, 20, + fsinfo->sector_size_info.out.byte_off_sector_align); + SIVAL(blob->data, 24, + fsinfo->sector_size_info.out.byte_off_partition_align); + + return NT_STATUS_OK; + + default: + return NT_STATUS_INVALID_LEVEL; + } +} + +NTSTATUS smbsrv_push_passthru_fileinfo(TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, + enum smb_fileinfo_level level, + union smb_fileinfo *st, + int default_str_flags) +{ + unsigned int i; + size_t list_size; + + switch (level) { + case RAW_FILEINFO_BASIC_INFORMATION: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 40)); + + push_nttime(blob->data, 0, st->basic_info.out.create_time); + push_nttime(blob->data, 8, st->basic_info.out.access_time); + push_nttime(blob->data, 16, st->basic_info.out.write_time); + push_nttime(blob->data, 24, st->basic_info.out.change_time); + SIVAL(blob->data, 32, st->basic_info.out.attrib); + SIVAL(blob->data, 36, 0); /* padding */ + return NT_STATUS_OK; + + case RAW_FILEINFO_NETWORK_OPEN_INFORMATION: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 56)); + + push_nttime(blob->data, 0, st->network_open_information.out.create_time); + push_nttime(blob->data, 8, st->network_open_information.out.access_time); + push_nttime(blob->data, 16, st->network_open_information.out.write_time); + push_nttime(blob->data, 24, st->network_open_information.out.change_time); + SBVAL(blob->data, 32, st->network_open_information.out.alloc_size); + SBVAL(blob->data, 40, st->network_open_information.out.size); + SIVAL(blob->data, 48, st->network_open_information.out.attrib); + SIVAL(blob->data, 52, 0); /* padding */ + return NT_STATUS_OK; + + case RAW_FILEINFO_STANDARD_INFORMATION: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 24)); + + SBVAL(blob->data, 0, st->standard_info.out.alloc_size); + SBVAL(blob->data, 8, st->standard_info.out.size); + SIVAL(blob->data, 16, st->standard_info.out.nlink); + SCVAL(blob->data, 20, st->standard_info.out.delete_pending); + SCVAL(blob->data, 21, st->standard_info.out.directory); + SSVAL(blob->data, 22, 0); /* padding */ + return NT_STATUS_OK; + + case RAW_FILEINFO_ATTRIBUTE_TAG_INFORMATION: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 8)); + + SIVAL(blob->data, 0, st->attribute_tag_information.out.attrib); + SIVAL(blob->data, 4, st->attribute_tag_information.out.reparse_tag); + return NT_STATUS_OK; + + case RAW_FILEINFO_EA_INFORMATION: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 4)); + + SIVAL(blob->data, 0, st->ea_info.out.ea_size); + return NT_STATUS_OK; + + case RAW_FILEINFO_MODE_INFORMATION: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 4)); + + SIVAL(blob->data, 0, st->mode_information.out.mode); + return NT_STATUS_OK; + + case RAW_FILEINFO_ALIGNMENT_INFORMATION: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 4)); + + SIVAL(blob->data, 0, + st->alignment_information.out.alignment_requirement); + return NT_STATUS_OK; + + case RAW_FILEINFO_ACCESS_INFORMATION: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 4)); + + SIVAL(blob->data, 0, st->access_information.out.access_flags); + return NT_STATUS_OK; + + case RAW_FILEINFO_POSITION_INFORMATION: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 8)); + + SBVAL(blob->data, 0, st->position_information.out.position); + return NT_STATUS_OK; + + case RAW_FILEINFO_COMPRESSION_INFORMATION: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 16)); + + SBVAL(blob->data, 0, st->compression_info.out.compressed_size); + SSVAL(blob->data, 8, st->compression_info.out.format); + SCVAL(blob->data, 10, st->compression_info.out.unit_shift); + SCVAL(blob->data, 11, st->compression_info.out.chunk_shift); + SCVAL(blob->data, 12, st->compression_info.out.cluster_shift); + SSVAL(blob->data, 13, 0); /* 3 bytes padding */ + SCVAL(blob->data, 15, 0); + return NT_STATUS_OK; + + case RAW_FILEINFO_INTERNAL_INFORMATION: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 8)); + + SBVAL(blob->data, 0, st->internal_information.out.file_id); + return NT_STATUS_OK; + + case RAW_FILEINFO_ALL_INFORMATION: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 72)); + + push_nttime(blob->data, 0, st->all_info.out.create_time); + push_nttime(blob->data, 8, st->all_info.out.access_time); + push_nttime(blob->data, 16, st->all_info.out.write_time); + push_nttime(blob->data, 24, st->all_info.out.change_time); + SIVAL(blob->data, 32, st->all_info.out.attrib); + SIVAL(blob->data, 36, 0); /* padding */ + SBVAL(blob->data, 40, st->all_info.out.alloc_size); + SBVAL(blob->data, 48, st->all_info.out.size); + SIVAL(blob->data, 56, st->all_info.out.nlink); + SCVAL(blob->data, 60, st->all_info.out.delete_pending); + SCVAL(blob->data, 61, st->all_info.out.directory); + SSVAL(blob->data, 62, 0); /* padding */ + SIVAL(blob->data, 64, st->all_info.out.ea_size); + BLOB_CHECK(smbsrv_blob_append_string(mem_ctx, blob, + st->all_info.out.fname.s, + 68, default_str_flags, + STR_UNICODE)); + return NT_STATUS_OK; + + case RAW_FILEINFO_NAME_INFORMATION: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 4)); + + BLOB_CHECK(smbsrv_blob_append_string(mem_ctx, blob, + st->name_info.out.fname.s, + 0, default_str_flags, + STR_UNICODE)); + return NT_STATUS_OK; + + case RAW_FILEINFO_ALT_NAME_INFORMATION: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 4)); + + BLOB_CHECK(smbsrv_blob_append_string(mem_ctx, blob, + st->alt_name_info.out.fname.s, + 0, default_str_flags, + STR_UNICODE)); + return NT_STATUS_OK; + + case RAW_FILEINFO_STREAM_INFORMATION: + for (i=0;i<st->stream_info.out.num_streams;i++) { + uint32_t data_size = blob->length; + uint8_t *data; + + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, data_size + 24)); + data = blob->data + data_size; + SBVAL(data, 8, st->stream_info.out.streams[i].size); + SBVAL(data, 16, st->stream_info.out.streams[i].alloc_size); + BLOB_CHECK(smbsrv_blob_append_string(mem_ctx, blob, + st->stream_info.out.streams[i].stream_name.s, + data_size + 4, default_str_flags, + STR_UNICODE)); + if (i == st->stream_info.out.num_streams - 1) { + SIVAL(blob->data, data_size, 0); + } else { + BLOB_CHECK(smbsrv_blob_fill_data(mem_ctx, blob, (blob->length+7)&~7)); + SIVAL(blob->data, data_size, + blob->length - data_size); + } + } + return NT_STATUS_OK; + + case RAW_FILEINFO_SMB2_ALL_EAS: + /* if no eas are returned the backend should + * have returned NO_EAS_ON_FILE or NO_MORE_EAS + * + * so it's a programmer error if num_eas == 0 + */ + if (st->all_eas.out.num_eas == 0) { + smb_panic("0 eas for SMB2_ALL_EAS - programmer error in ntvfs backend"); + } + + list_size = ea_list_size_chained(st->all_eas.out.num_eas, + st->all_eas.out.eas, 4); + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, list_size)); + + ea_put_list_chained(blob->data, + st->all_eas.out.num_eas, + st->all_eas.out.eas, 4); + return NT_STATUS_OK; + + case RAW_FILEINFO_SMB2_ALL_INFORMATION: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 0x64)); + + push_nttime(blob->data, 0x00, st->all_info2.out.create_time); + push_nttime(blob->data, 0x08, st->all_info2.out.access_time); + push_nttime(blob->data, 0x10, st->all_info2.out.write_time); + push_nttime(blob->data, 0x18, st->all_info2.out.change_time); + SIVAL(blob->data, 0x20, st->all_info2.out.attrib); + SIVAL(blob->data, 0x24, st->all_info2.out.unknown1); + SBVAL(blob->data, 0x28, st->all_info2.out.alloc_size); + SBVAL(blob->data, 0x30, st->all_info2.out.size); + SIVAL(blob->data, 0x38, st->all_info2.out.nlink); + SCVAL(blob->data, 0x3C, st->all_info2.out.delete_pending); + SCVAL(blob->data, 0x3D, st->all_info2.out.directory); + SSVAL(blob->data, 0x3E, 0); /* padding */ + SBVAL(blob->data, 0x40, st->all_info2.out.file_id); + SIVAL(blob->data, 0x48, st->all_info2.out.ea_size); + SIVAL(blob->data, 0x4C, st->all_info2.out.access_mask); + SBVAL(blob->data, 0x50, st->all_info2.out.position); + SIVAL(blob->data, 0x58, st->all_info2.out.mode); + SIVAL(blob->data, 0x5C, st->all_info2.out.alignment_requirement); + BLOB_CHECK(smbsrv_blob_append_string(mem_ctx, blob, + st->all_info2.out.fname.s, + 0x60, default_str_flags, + STR_UNICODE)); + return NT_STATUS_OK; + + default: + return NT_STATUS_INVALID_LEVEL; + } +} + +NTSTATUS smbsrv_pull_passthru_sfileinfo(TALLOC_CTX *mem_ctx, + enum smb_setfileinfo_level level, + union smb_setfileinfo *st, + const DATA_BLOB *blob, + int default_str_flags, + struct request_bufinfo *bufinfo) +{ + uint32_t len, ofs; + DATA_BLOB str_blob; + + switch (level) { + case SMB_SFILEINFO_BASIC_INFORMATION: + BLOB_CHECK_MIN_SIZE(blob, 40); + + st->basic_info.in.create_time = pull_nttime(blob->data, 0); + st->basic_info.in.access_time = pull_nttime(blob->data, 8); + st->basic_info.in.write_time = pull_nttime(blob->data, 16); + st->basic_info.in.change_time = pull_nttime(blob->data, 24); + st->basic_info.in.attrib = IVAL(blob->data, 32); + st->basic_info.in.reserved = IVAL(blob->data, 36); + + return NT_STATUS_OK; + + case SMB_SFILEINFO_DISPOSITION_INFORMATION: + BLOB_CHECK_MIN_SIZE(blob, 1); + + st->disposition_info.in.delete_on_close = CVAL(blob->data, 0); + + return NT_STATUS_OK; + + case SMB_SFILEINFO_ALLOCATION_INFORMATION: + BLOB_CHECK_MIN_SIZE(blob, 8); + + st->allocation_info.in.alloc_size = BVAL(blob->data, 0); + + return NT_STATUS_OK; + + case RAW_SFILEINFO_END_OF_FILE_INFORMATION: + BLOB_CHECK_MIN_SIZE(blob, 8); + + st->end_of_file_info.in.size = BVAL(blob->data, 0); + + return NT_STATUS_OK; + + case RAW_SFILEINFO_RENAME_INFORMATION: + if (!bufinfo) { + return NT_STATUS_INTERNAL_ERROR; + } + BLOB_CHECK_MIN_SIZE(blob, 12); + st->rename_information.in.overwrite = CVAL(blob->data, 0); + st->rename_information.in.root_fid = IVAL(blob->data, 4); + len = IVAL(blob->data, 8); + ofs = 12; + str_blob = *blob; + str_blob.length = MIN(str_blob.length, ofs+len); + smbsrv_blob_pull_string(bufinfo, &str_blob, ofs, + &st->rename_information.in.new_name, + STR_UNICODE); + if (st->rename_information.in.new_name == NULL) { + return NT_STATUS_FOOBAR; + } + + return NT_STATUS_OK; + + + case RAW_SFILEINFO_LINK_INFORMATION: + if (!bufinfo) { + return NT_STATUS_INTERNAL_ERROR; + } + BLOB_CHECK_MIN_SIZE(blob, 20); + st->link_information.in.overwrite = CVAL(blob->data, 0); + st->link_information.in.root_fid = IVAL(blob->data, 8); + len = IVAL(blob->data, 16); + ofs = 20; + str_blob = *blob; + str_blob.length = MIN(str_blob.length, ofs+len); + smbsrv_blob_pull_string(bufinfo, &str_blob, ofs, + &st->link_information.in.new_name, + STR_UNICODE); + if (st->link_information.in.new_name == NULL) { + return NT_STATUS_FOOBAR; + } + + return NT_STATUS_OK; + + case RAW_SFILEINFO_RENAME_INFORMATION_SMB2: + /* SMB2 uses a different format for rename information */ + if (!bufinfo) { + return NT_STATUS_INTERNAL_ERROR; + } + BLOB_CHECK_MIN_SIZE(blob, 20); + st->rename_information.in.overwrite = CVAL(blob->data, 0); + st->rename_information.in.root_fid = BVAL(blob->data, 8); + len = IVAL(blob->data,16); + ofs = 20; + str_blob = *blob; + str_blob.length = MIN(str_blob.length, ofs+len); + smbsrv_blob_pull_string(bufinfo, &str_blob, ofs, + &st->rename_information.in.new_name, + STR_UNICODE); + if (st->rename_information.in.new_name == NULL) { + return NT_STATUS_FOOBAR; + } + + return NT_STATUS_OK; + + case RAW_SFILEINFO_POSITION_INFORMATION: + BLOB_CHECK_MIN_SIZE(blob, 8); + + st->position_information.in.position = BVAL(blob->data, 0); + + return NT_STATUS_OK; + + case RAW_SFILEINFO_FULL_EA_INFORMATION: + return ea_pull_list_chained(blob, + mem_ctx, + &st->full_ea_information.in.eas.num_eas, + &st->full_ea_information.in.eas.eas); + + case RAW_SFILEINFO_MODE_INFORMATION: + BLOB_CHECK_MIN_SIZE(blob, 4); + + st->mode_information.in.mode = IVAL(blob->data, 0); + + return NT_STATUS_OK; + + default: + return NT_STATUS_INVALID_LEVEL; + } +} + +/* + fill a single entry in a trans2 find reply +*/ +NTSTATUS smbsrv_push_passthru_search(TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, + enum smb_search_data_level level, + const union smb_search_data *file, + int default_str_flags) +{ + uint8_t *data; + unsigned int ofs = blob->length; + + switch (level) { + case RAW_SEARCH_DATA_DIRECTORY_INFO: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, ofs + 64)); + data = blob->data + ofs; + SIVAL(data, 4, file->directory_info.file_index); + push_nttime(data, 8, file->directory_info.create_time); + push_nttime(data, 16, file->directory_info.access_time); + push_nttime(data, 24, file->directory_info.write_time); + push_nttime(data, 32, file->directory_info.change_time); + SBVAL(data, 40, file->directory_info.size); + SBVAL(data, 48, file->directory_info.alloc_size); + SIVAL(data, 56, file->directory_info.attrib); + BLOB_CHECK(smbsrv_blob_append_string(mem_ctx, blob, file->directory_info.name.s, + ofs + 60, default_str_flags, + STR_TERMINATE_ASCII)); + BLOB_ALIGN(blob, 8); + data = blob->data + ofs; + SIVAL(data, 0, blob->length - ofs); + return NT_STATUS_OK; + + case RAW_SEARCH_DATA_FULL_DIRECTORY_INFO: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, ofs + 68)); + data = blob->data + ofs; + SIVAL(data, 4, file->full_directory_info.file_index); + push_nttime(data, 8, file->full_directory_info.create_time); + push_nttime(data, 16, file->full_directory_info.access_time); + push_nttime(data, 24, file->full_directory_info.write_time); + push_nttime(data, 32, file->full_directory_info.change_time); + SBVAL(data, 40, file->full_directory_info.size); + SBVAL(data, 48, file->full_directory_info.alloc_size); + SIVAL(data, 56, file->full_directory_info.attrib); + SIVAL(data, 64, file->full_directory_info.ea_size); + BLOB_CHECK(smbsrv_blob_append_string(mem_ctx, blob, file->full_directory_info.name.s, + ofs + 60, default_str_flags, + STR_TERMINATE_ASCII)); + BLOB_ALIGN(blob, 8); + data = blob->data + ofs; + SIVAL(data, 0, blob->length - ofs); + return NT_STATUS_OK; + + case RAW_SEARCH_DATA_NAME_INFO: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, ofs + 12)); + data = blob->data + ofs; + SIVAL(data, 4, file->name_info.file_index); + BLOB_CHECK(smbsrv_blob_append_string(mem_ctx, blob, file->name_info.name.s, + ofs + 8, default_str_flags, + STR_TERMINATE_ASCII)); + BLOB_ALIGN(blob, 8); + data = blob->data + ofs; + SIVAL(data, 0, blob->length - ofs); + return NT_STATUS_OK; + + case RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, ofs + 94)); + data = blob->data + ofs; + SIVAL(data, 4, file->both_directory_info.file_index); + push_nttime(data, 8, file->both_directory_info.create_time); + push_nttime(data, 16, file->both_directory_info.access_time); + push_nttime(data, 24, file->both_directory_info.write_time); + push_nttime(data, 32, file->both_directory_info.change_time); + SBVAL(data, 40, file->both_directory_info.size); + SBVAL(data, 48, file->both_directory_info.alloc_size); + SIVAL(data, 56, file->both_directory_info.attrib); + SIVAL(data, 64, file->both_directory_info.ea_size); + SCVAL(data, 69, 0); /* reserved */ + memset(data+70,0,24); + smbsrv_blob_push_string(mem_ctx, blob, + 68 + ofs, 70 + ofs, + file->both_directory_info.short_name.s, + 24, default_str_flags, + STR_UNICODE | STR_LEN8BIT); + BLOB_CHECK(smbsrv_blob_append_string(mem_ctx, blob, file->both_directory_info.name.s, + ofs + 60, default_str_flags, + STR_TERMINATE_ASCII)); + BLOB_ALIGN(blob, 8); + data = blob->data + ofs; + SIVAL(data, 0, blob->length - ofs); + return NT_STATUS_OK; + + case RAW_SEARCH_DATA_ID_FULL_DIRECTORY_INFO: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, ofs + 80)); + data = blob->data + ofs; + SIVAL(data, 4, file->id_full_directory_info.file_index); + push_nttime(data, 8, file->id_full_directory_info.create_time); + push_nttime(data, 16, file->id_full_directory_info.access_time); + push_nttime(data, 24, file->id_full_directory_info.write_time); + push_nttime(data, 32, file->id_full_directory_info.change_time); + SBVAL(data, 40, file->id_full_directory_info.size); + SBVAL(data, 48, file->id_full_directory_info.alloc_size); + SIVAL(data, 56, file->id_full_directory_info.attrib); + SIVAL(data, 64, file->id_full_directory_info.ea_size); + SIVAL(data, 68, 0); /* padding */ + SBVAL(data, 72, file->id_full_directory_info.file_id); + BLOB_CHECK(smbsrv_blob_append_string(mem_ctx, blob, file->id_full_directory_info.name.s, + ofs + 60, default_str_flags, + STR_TERMINATE_ASCII)); + BLOB_ALIGN(blob, 8); + data = blob->data + ofs; + SIVAL(data, 0, blob->length - ofs); + return NT_STATUS_OK; + + case RAW_SEARCH_DATA_ID_BOTH_DIRECTORY_INFO: + BLOB_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, ofs + 104)); + data = blob->data + ofs; + SIVAL(data, 4, file->id_both_directory_info.file_index); + push_nttime(data, 8, file->id_both_directory_info.create_time); + push_nttime(data, 16, file->id_both_directory_info.access_time); + push_nttime(data, 24, file->id_both_directory_info.write_time); + push_nttime(data, 32, file->id_both_directory_info.change_time); + SBVAL(data, 40, file->id_both_directory_info.size); + SBVAL(data, 48, file->id_both_directory_info.alloc_size); + SIVAL(data, 56, file->id_both_directory_info.attrib); + SIVAL(data, 64, file->id_both_directory_info.ea_size); + SCVAL(data, 69, 0); /* reserved */ + memset(data+70,0,26); + smbsrv_blob_push_string(mem_ctx, blob, + 68 + ofs, 70 + ofs, + file->id_both_directory_info.short_name.s, + 24, default_str_flags, + STR_UNICODE | STR_LEN8BIT); + SBVAL(data, 96, file->id_both_directory_info.file_id); + BLOB_CHECK(smbsrv_blob_append_string(mem_ctx, blob, file->id_both_directory_info.name.s, + ofs + 60, default_str_flags, + STR_TERMINATE_ASCII)); + BLOB_ALIGN(blob, 8); + data = blob->data + ofs; + SIVAL(data, 0, blob->length - ofs); + return NT_STATUS_OK; + + default: + return NT_STATUS_INVALID_LEVEL; + } +} diff --git a/source4/smb_server/handle.c b/source4/smb_server/handle.c new file mode 100644 index 0000000..931f77a --- /dev/null +++ b/source4/smb_server/handle.c @@ -0,0 +1,142 @@ +/* + Unix SMB/CIFS implementation. + Manage smbsrv_handle structures + 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 "smb_server/smb_server.h" + + +/**************************************************************************** +init the handle structures +****************************************************************************/ +NTSTATUS smbsrv_init_handles(struct smbsrv_tcon *tcon, uint32_t limit) +{ + /* + * the idr_* functions take 'int' as limit, + * and only work with a max limit 0x00FFFFFF + */ + limit &= 0x00FFFFFF; + + tcon->handles.idtree_hid = idr_init(tcon); + NT_STATUS_HAVE_NO_MEMORY(tcon->handles.idtree_hid); + tcon->handles.idtree_limit = limit; + tcon->handles.list = NULL; + + return NT_STATUS_OK; +} + +/**************************************************************************** +find a handle given a handle id +****************************************************************************/ +static struct smbsrv_handle *smbsrv_handle_find(struct smbsrv_handles_context *handles_ctx, + uint32_t hid, struct timeval request_time) +{ + void *p; + struct smbsrv_handle *handle; + + if (hid == 0) return NULL; + + if (hid > handles_ctx->idtree_limit) return NULL; + + p = idr_find(handles_ctx->idtree_hid, hid); + if (!p) return NULL; + + handle = talloc_get_type(p, struct smbsrv_handle); + if (!handle) return NULL; + + /* only give it away when the ntvfs subsystem has made the handle valid */ + if (!handle->ntvfs) return NULL; + + handle->statistics.last_use_time = request_time; + + return handle; +} + +struct smbsrv_handle *smbsrv_smb_handle_find(struct smbsrv_tcon *smb_tcon, + uint16_t fnum, struct timeval request_time) +{ + return smbsrv_handle_find(&smb_tcon->handles, fnum, request_time); +} + +struct smbsrv_handle *smbsrv_smb2_handle_find(struct smbsrv_tcon *smb_tcon, + uint32_t hid, struct timeval request_time) +{ + return smbsrv_handle_find(&smb_tcon->handles, hid, request_time); +} + +/* + destroy a connection structure +*/ +static int smbsrv_handle_destructor(struct smbsrv_handle *handle) +{ + struct smbsrv_handles_context *handles_ctx; + + handles_ctx = &handle->tcon->handles; + + idr_remove(handles_ctx->idtree_hid, handle->hid); + DLIST_REMOVE(handles_ctx->list, handle); + DLIST_REMOVE(handle->session->handles, &handle->session_item); + + /* tell the ntvfs backend that we are disconnecting */ + if (handle->ntvfs) { + talloc_free(handle->ntvfs); + handle->ntvfs = NULL; + } + + return 0; +} + +/* + find first available handle slot +*/ +struct smbsrv_handle *smbsrv_handle_new(struct smbsrv_session *session, + struct smbsrv_tcon *tcon, + TALLOC_CTX *mem_ctx, + struct timeval request_time) +{ + struct smbsrv_handles_context *handles_ctx = &tcon->handles; + struct smbsrv_handle *handle; + int i; + + handle = talloc_zero(mem_ctx, struct smbsrv_handle); + if (!handle) return NULL; + handle->tcon = tcon; + handle->session = session; + + i = idr_get_new_above(handles_ctx->idtree_hid, handle, 1, handles_ctx->idtree_limit); + if (i == -1) { + DEBUG(1,("ERROR! Out of handle structures\n")); + goto failed; + } + handle->hid = i; + handle->session_item.handle = handle; + + DLIST_ADD(handles_ctx->list, handle); + DLIST_ADD(session->handles, &handle->session_item); + talloc_set_destructor(handle, smbsrv_handle_destructor); + + /* now fill in some statistics */ + handle->statistics.open_time = request_time; + handle->statistics.last_use_time = request_time; + + return handle; + +failed: + talloc_free(handle); + return NULL; +} diff --git a/source4/smb_server/management.c b/source4/smb_server/management.c new file mode 100644 index 0000000..5cdfff0 --- /dev/null +++ b/source4/smb_server/management.c @@ -0,0 +1,138 @@ +/* + Unix SMB/CIFS implementation. + + management calls for smb server + + Copyright (C) Andrew Tridgell 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "smb_server/smb_server.h" +#include "samba/service_stream.h" +#include "lib/messaging/irpc.h" +#include "librpc/gen_ndr/ndr_irpc.h" +#include "auth/auth.h" +#include "lib/tsocket/tsocket.h" + +/* + return a list of open sessions +*/ +static NTSTATUS smbsrv_session_information(struct irpc_message *msg, + struct smbsrv_information *r) +{ + struct smbsrv_connection *smb_conn = talloc_get_type(msg->private_data, + struct smbsrv_connection); + struct tsocket_address *client_addr = smb_conn->connection->remote_address; + char *client_addr_string; + int i=0, count=0; + struct smbsrv_session *sess; + + /* This is for debugging only! */ + client_addr_string = tsocket_address_string(client_addr, r); + NT_STATUS_HAVE_NO_MEMORY(client_addr_string); + + /* count the number of sessions */ + for (sess=smb_conn->sessions.list; sess; sess=sess->next) { + count++; + } + + r->out.info.sessions.num_sessions = count; + r->out.info.sessions.sessions = talloc_array(r, struct smbsrv_session_info, count); + NT_STATUS_HAVE_NO_MEMORY(r->out.info.sessions.sessions); + + for (sess=smb_conn->sessions.list; sess; sess=sess->next) { + struct smbsrv_session_info *info = &r->out.info.sessions.sessions[i]; + + info->client_ip = client_addr_string; + + info->vuid = sess->vuid; + info->account_name = sess->session_info->info->account_name; + info->domain_name = sess->session_info->info->domain_name; + + info->connect_time = timeval_to_nttime(&sess->statistics.connect_time); + info->auth_time = timeval_to_nttime(&sess->statistics.auth_time); + info->last_use_time= timeval_to_nttime(&sess->statistics.last_request_time); + i++; + } + + return NT_STATUS_OK; +} + +/* + return a list of tree connects +*/ +static NTSTATUS smbsrv_tcon_information(struct irpc_message *msg, + struct smbsrv_information *r) +{ + struct smbsrv_connection *smb_conn = talloc_get_type(msg->private_data, + struct smbsrv_connection); + struct tsocket_address *client_addr = smb_conn->connection->remote_address; + char *client_addr_string; + int i=0, count=0; + struct smbsrv_tcon *tcon; + + /* This is for debugging only! */ + client_addr_string = tsocket_address_string(client_addr, r); + NT_STATUS_HAVE_NO_MEMORY(client_addr_string); + + /* count the number of tcons */ + for (tcon=smb_conn->smb_tcons.list; tcon; tcon=tcon->next) { + count++; + } + + r->out.info.tcons.num_tcons = count; + r->out.info.tcons.tcons = talloc_array(r, struct smbsrv_tcon_info, count); + NT_STATUS_HAVE_NO_MEMORY(r->out.info.tcons.tcons); + + for (tcon=smb_conn->smb_tcons.list; tcon; tcon=tcon->next) { + struct smbsrv_tcon_info *info = &r->out.info.tcons.tcons[i]; + + info->client_ip = client_addr_string; + + info->tid = tcon->tid; + info->share_name = tcon->share_name; + info->connect_time = timeval_to_nttime(&tcon->statistics.connect_time); + info->last_use_time= timeval_to_nttime(&tcon->statistics.last_request_time); + i++; + } + + return NT_STATUS_OK; +} + +/* + serve smbserver information via irpc +*/ +static NTSTATUS smbsrv_information(struct irpc_message *msg, + struct smbsrv_information *r) +{ + switch (r->in.level) { + case SMBSRV_INFO_SESSIONS: + return smbsrv_session_information(msg, r); + case SMBSRV_INFO_TCONS: + return smbsrv_tcon_information(msg, r); + } + + return NT_STATUS_OK; +} + +/* + initialise irpc management calls on a connection +*/ +void smbsrv_management_init(struct smbsrv_connection *smb_conn) +{ + IRPC_REGISTER(smb_conn->connection->msg_ctx, irpc, SMBSRV_INFORMATION, + smbsrv_information, smb_conn); +} diff --git a/source4/smb_server/service_smb.c b/source4/smb_server/service_smb.c new file mode 100644 index 0000000..05e2623 --- /dev/null +++ b/source4/smb_server/service_smb.c @@ -0,0 +1,110 @@ +/* + Unix SMB/CIFS implementation. + process incoming packets - main loop + Copyright (C) Andrew Tridgell 2004-2005 + Copyright (C) Stefan Metzmacher 2004-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 "samba/service_task.h" +#include "samba/service_stream.h" +#include "samba/service.h" +#include "smb_server/smb_server.h" +#include "smb_server/service_smb_proto.h" +#include "lib/messaging/irpc.h" +#include "lib/stream/packet.h" +#include "libcli/smb2/smb2.h" +#include "smb_server/smb2/smb2_server.h" +#include "system/network.h" +#include "lib/socket/netif.h" +#include "param/share.h" +#include "dsdb/samdb/samdb.h" +#include "param/param.h" +#include "ntvfs/ntvfs.h" +#include "lib/cmdline/cmdline.h" + +/* + open the smb server sockets +*/ +static NTSTATUS smbsrv_task_init(struct task_server *task) +{ + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + + task_server_set_title(task, "task[smbsrv]"); + + if (lpcfg_interfaces(task->lp_ctx) && lpcfg_bind_interfaces_only(task->lp_ctx)) { + int num_interfaces; + int i; + struct interface *ifaces; + + load_interface_list(task, task->lp_ctx, &ifaces); + + num_interfaces = iface_list_count(ifaces); + + /* We have been given an interfaces line, and been + told to only bind to those interfaces. Create a + socket per interface and bind to only these. + */ + for(i = 0; i < num_interfaces; i++) { + const char *address = iface_list_n_ip(ifaces, i); + status = smbsrv_add_socket(task, task->event_ctx, + task->lp_ctx, + task->model_ops, + address, + task->process_context); + if (!NT_STATUS_IS_OK(status)) goto failed; + } + } else { + char **wcard; + int i; + wcard = iface_list_wildcard(task); + if (wcard == NULL) { + DEBUG(0,("No wildcard addresses available\n")); + status = NT_STATUS_UNSUCCESSFUL; + goto failed; + } + for (i=0; wcard[i]; i++) { + status = smbsrv_add_socket(task, task->event_ctx, + task->lp_ctx, + task->model_ops, + wcard[i], + task->process_context); + if (!NT_STATUS_IS_OK(status)) goto failed; + } + talloc_free(wcard); + } + + irpc_add_name(task->msg_ctx, "smb_server"); + return NT_STATUS_OK; +failed: + task_server_terminate(task, "Failed to startup smb server task", true); + return status; +} + +/* called at smbd startup - register ourselves as a server service */ +NTSTATUS server_service_smb_init(TALLOC_CTX *ctx) +{ + struct loadparm_context *lp_ctx = samba_cmdline_get_lp_ctx(); + static const struct service_details details = { + .inhibit_fork_on_accept = true, + .inhibit_pre_fork = true, + .task_init = smbsrv_task_init, + .post_fork = NULL + }; + ntvfs_init(lp_ctx); + share_init(); + return register_server_service(ctx, "smb", &details); +} diff --git a/source4/smb_server/session.c b/source4/smb_server/session.c new file mode 100644 index 0000000..321d7dd --- /dev/null +++ b/source4/smb_server/session.c @@ -0,0 +1,162 @@ +/* + Unix SMB/CIFS implementation. + Password and authentication handling + Copyright (C) Andrew Tridgell 1992-2005 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005 + Copyright (C) Stefan Metzmacher 2005-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 "smb_server/smb_server.h" + + +/* + * init the sessions structures + */ +NTSTATUS smbsrv_init_sessions(struct smbsrv_connection *smb_conn, uint64_t limit) +{ + /* + * the idr_* functions take 'int' as limit, + * and only work with a max limit 0x00FFFFFF + */ + limit &= 0x00FFFFFF; + + smb_conn->sessions.idtree_vuid = idr_init(smb_conn); + NT_STATUS_HAVE_NO_MEMORY(smb_conn->sessions.idtree_vuid); + smb_conn->sessions.idtree_limit = limit; + smb_conn->sessions.list = NULL; + + return NT_STATUS_OK; +} + +/* + * Find the session structure associated with a VUID + * (not one from an in-progress session setup) + */ +struct smbsrv_session *smbsrv_session_find(struct smbsrv_connection *smb_conn, + uint64_t vuid, struct timeval request_time) +{ + void *p; + struct smbsrv_session *sess; + + if (vuid == 0) return NULL; + + if (vuid > smb_conn->sessions.idtree_limit) return NULL; + + p = idr_find(smb_conn->sessions.idtree_vuid, vuid); + if (!p) return NULL; + + /* only return a finished session */ + sess = talloc_get_type(p, struct smbsrv_session); + if (sess && sess->session_info) { + sess->statistics.last_request_time = request_time; + return sess; + } + + return NULL; +} + +/* + * Find the session structure associated with a VUID + * (associated with an in-progress session setup) + */ +struct smbsrv_session *smbsrv_session_find_sesssetup(struct smbsrv_connection *smb_conn, uint64_t vuid) +{ + void *p; + struct smbsrv_session *sess; + + if (vuid == 0) return NULL; + + if (vuid > smb_conn->sessions.idtree_limit) return NULL; + + p = idr_find(smb_conn->sessions.idtree_vuid, vuid); + if (!p) return NULL; + + sess = talloc_get_type_abort(p, struct smbsrv_session); + + return sess; +} + +/* + * the session will be marked as valid for usage + * by attaching a auth_session_info to the session. + * + * session_info will be talloc_stealed + */ +NTSTATUS smbsrv_session_sesssetup_finished(struct smbsrv_session *sess, + struct auth_session_info *session_info) +{ + /* this check is to catch programmer errors */ + if (!session_info) { + talloc_free(sess); + return NT_STATUS_ACCESS_DENIED; + } + + /* mark the session as successful authenticated */ + sess->session_info = talloc_steal(sess, session_info); + + /* now fill in some statistics */ + sess->statistics.auth_time = timeval_current(); + + return NT_STATUS_OK; +} + +/**************************************************************************** +destroy a session structure +****************************************************************************/ +static int smbsrv_session_destructor(struct smbsrv_session *sess) +{ + struct smbsrv_connection *smb_conn = sess->smb_conn; + + idr_remove(smb_conn->sessions.idtree_vuid, sess->vuid); + DLIST_REMOVE(smb_conn->sessions.list, sess); + return 0; +} + +/* + * allocate a new session structure with a VUID. + * gensec_ctx is optional, but talloc_steal'ed when present + */ +struct smbsrv_session *smbsrv_session_new(struct smbsrv_connection *smb_conn, + TALLOC_CTX *mem_ctx, + struct gensec_security *gensec_ctx) +{ + struct smbsrv_session *sess = NULL; + int i; + + sess = talloc_zero(mem_ctx, struct smbsrv_session); + if (!sess) return NULL; + sess->smb_conn = smb_conn; + + i = idr_get_new_random(smb_conn->sessions.idtree_vuid, sess, smb_conn->sessions.idtree_limit); + if (i == -1) { + DEBUG(1,("ERROR! Out of connection structures\n")); + talloc_free(sess); + return NULL; + } + sess->vuid = i; + + /* use this to keep tabs on all our info from the authentication */ + sess->gensec_ctx = talloc_steal(sess, gensec_ctx); + + DLIST_ADD(smb_conn->sessions.list, sess); + talloc_set_destructor(sess, smbsrv_session_destructor); + + /* now fill in some statistics */ + sess->statistics.connect_time = timeval_current(); + + return sess; +} diff --git a/source4/smb_server/smb/negprot.c b/source4/smb_server/smb/negprot.c new file mode 100644 index 0000000..cd2451d --- /dev/null +++ b/source4/smb_server/smb/negprot.c @@ -0,0 +1,564 @@ +/* + Unix SMB/CIFS implementation. + negprot reply code + Copyright (C) Andrew Tridgell 1992-1998 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "auth/credentials/credentials.h" +#include "auth/gensec/gensec.h" +#include "auth/auth.h" +#include "smb_server/smb_server.h" +#include "libcli/smb2/smb2.h" +#include "smb_server/smb2/smb2_server.h" +#include "samba/service_stream.h" +#include "lib/stream/packet.h" +#include "param/param.h" + + +/* initialise the auth_context for this server and return the cryptkey */ +static NTSTATUS get_challenge(struct smbsrv_connection *smb_conn, uint8_t buff[8]) +{ + NTSTATUS nt_status; + + /* muliple negprots are not premitted */ + if (smb_conn->negotiate.auth_context) { + DEBUG(3,("get challenge: is this a secondary negprot? auth_context is non-NULL!\n")); + return NT_STATUS_FOOBAR; + } + + DEBUG(10, ("get challenge: creating negprot_global_auth_context\n")); + + nt_status = auth_context_create(smb_conn, + smb_conn->connection->event.ctx, + smb_conn->connection->msg_ctx, + smb_conn->lp_ctx, + &smb_conn->negotiate.auth_context); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(0, ("auth_context_create() returned %s", nt_errstr(nt_status))); + return nt_status; + } + + nt_status = auth_get_challenge(smb_conn->negotiate.auth_context, buff); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(0, ("auth_get_challenge() returned %s", nt_errstr(nt_status))); + return nt_status; + } + + return NT_STATUS_OK; +} + +/**************************************************************************** + Reply for the core protocol. +****************************************************************************/ +static void reply_corep(struct smbsrv_request *req, uint16_t choice) +{ + smbsrv_setup_reply(req, 1, 0); + + SSVAL(req->out.vwv, VWV(0), choice); + + req->smb_conn->negotiate.protocol = PROTOCOL_CORE; + + if (req->smb_conn->signing.mandatory_signing) { + smbsrv_terminate_connection(req->smb_conn, + "CORE does not support SMB signing, and it is mandatory\n"); + return; + } + + smbsrv_send_reply(req); +} + +/**************************************************************************** + Reply for the coreplus protocol. +this is quite incomplete - we only fill in a small part of the reply, but as nobody uses +this any more it probably doesn't matter +****************************************************************************/ +static void reply_coreplus(struct smbsrv_request *req, uint16_t choice) +{ + uint16_t raw; + if (lpcfg_async_smb_echo_handler(req->smb_conn->lp_ctx)) { + raw = 0; + } else { + raw = (lpcfg_read_raw(req->smb_conn->lp_ctx)?1:0) | + (lpcfg_write_raw(req->smb_conn->lp_ctx)?2:0); + } + + smbsrv_setup_reply(req, 13, 0); + + /* Reply, SMBlockread, SMBwritelock supported. */ + SCVAL(req->out.hdr,HDR_FLG, + CVAL(req->out.hdr, HDR_FLG) | FLAG_SUPPORT_LOCKREAD); + + SSVAL(req->out.vwv, VWV(0), choice); + SSVAL(req->out.vwv, VWV(1), 0x1); /* user level security, don't encrypt */ + + /* tell redirector we support + readbraw and writebraw (possibly) */ + SSVAL(req->out.vwv, VWV(5), raw); + + req->smb_conn->negotiate.protocol = PROTOCOL_COREPLUS; + + if (req->smb_conn->signing.mandatory_signing) { + smbsrv_terminate_connection(req->smb_conn, + "COREPLUS does not support SMB signing, and it is mandatory\n"); + return; + } + + smbsrv_send_reply(req); +} + +/**************************************************************************** + Reply for the lanman 1.0 protocol. +****************************************************************************/ +static void reply_lanman1(struct smbsrv_request *req, uint16_t choice) +{ + int secword=0; + time_t t = req->request_time.tv_sec; + uint16_t raw; + + if (lpcfg_async_smb_echo_handler(req->smb_conn->lp_ctx)) { + raw = 0; + } else { + raw = (lpcfg_read_raw(req->smb_conn->lp_ctx)?1:0) | + (lpcfg_write_raw(req->smb_conn->lp_ctx)?2:0); + } + + req->smb_conn->negotiate.encrypted_passwords = lpcfg_encrypt_passwords(req->smb_conn->lp_ctx); + + if (req->smb_conn->negotiate.encrypted_passwords) + secword |= NEGOTIATE_SECURITY_CHALLENGE_RESPONSE; + + req->smb_conn->negotiate.protocol = PROTOCOL_LANMAN1; + + smbsrv_setup_reply(req, 13, req->smb_conn->negotiate.encrypted_passwords ? 8 : 0); + + /* SMBlockread, SMBwritelock supported. */ + SCVAL(req->out.hdr,HDR_FLG, + CVAL(req->out.hdr, HDR_FLG) | FLAG_SUPPORT_LOCKREAD); + + SSVAL(req->out.vwv, VWV(0), choice); + SSVAL(req->out.vwv, VWV(1), secword); + SSVAL(req->out.vwv, VWV(2), req->smb_conn->negotiate.max_recv); + SSVAL(req->out.vwv, VWV(3), lpcfg_max_mux(req->smb_conn->lp_ctx)); + SSVAL(req->out.vwv, VWV(4), 1); + SSVAL(req->out.vwv, VWV(5), raw); + SIVAL(req->out.vwv, VWV(6), req->smb_conn->connection->server_id.pid); + srv_push_dos_date(req->smb_conn, req->out.vwv, VWV(8), t); + SSVAL(req->out.vwv, VWV(10), req->smb_conn->negotiate.zone_offset/60); + SIVAL(req->out.vwv, VWV(11), 0); /* reserved */ + + /* Create a token value and add it to the outgoing packet. */ + if (req->smb_conn->negotiate.encrypted_passwords) { + NTSTATUS nt_status; + + SSVAL(req->out.vwv, VWV(11), 8); + + nt_status = get_challenge(req->smb_conn, req->out.data); + if (!NT_STATUS_IS_OK(nt_status)) { + smbsrv_terminate_connection(req->smb_conn, "LANMAN1 get_challenge failed\n"); + return; + } + } + + if (req->smb_conn->signing.mandatory_signing) { + smbsrv_terminate_connection(req->smb_conn, + "LANMAN1 does not support SMB signing, and it is mandatory\n"); + return; + } + + smbsrv_send_reply(req); +} + +/**************************************************************************** + Reply for the lanman 2.0 protocol. +****************************************************************************/ +static void reply_lanman2(struct smbsrv_request *req, uint16_t choice) +{ + int secword=0; + time_t t = req->request_time.tv_sec; + uint16_t raw; + if (lpcfg_async_smb_echo_handler(req->smb_conn->lp_ctx)) { + raw = 0; + } else { + raw = (lpcfg_read_raw(req->smb_conn->lp_ctx)?1:0) | + (lpcfg_write_raw(req->smb_conn->lp_ctx)?2:0); + } + + req->smb_conn->negotiate.encrypted_passwords = lpcfg_encrypt_passwords(req->smb_conn->lp_ctx); + + if (req->smb_conn->negotiate.encrypted_passwords) + secword |= NEGOTIATE_SECURITY_CHALLENGE_RESPONSE; + + req->smb_conn->negotiate.protocol = PROTOCOL_LANMAN2; + + smbsrv_setup_reply(req, 13, 0); + + SSVAL(req->out.vwv, VWV(0), choice); + SSVAL(req->out.vwv, VWV(1), secword); + SSVAL(req->out.vwv, VWV(2), req->smb_conn->negotiate.max_recv); + SSVAL(req->out.vwv, VWV(3), lpcfg_max_mux(req->smb_conn->lp_ctx)); + SSVAL(req->out.vwv, VWV(4), 1); + SSVAL(req->out.vwv, VWV(5), raw); + SIVAL(req->out.vwv, VWV(6), req->smb_conn->connection->server_id.pid); + srv_push_dos_date(req->smb_conn, req->out.vwv, VWV(8), t); + SSVAL(req->out.vwv, VWV(10), req->smb_conn->negotiate.zone_offset/60); + SIVAL(req->out.vwv, VWV(11), 0); + + /* Create a token value and add it to the outgoing packet. */ + if (req->smb_conn->negotiate.encrypted_passwords) { + SSVAL(req->out.vwv, VWV(11), 8); + req_grow_data(req, 8); + get_challenge(req->smb_conn, req->out.data); + } + + req_push_str(req, NULL, lpcfg_workgroup(req->smb_conn->lp_ctx), -1, STR_TERMINATE); + + if (req->smb_conn->signing.mandatory_signing) { + smbsrv_terminate_connection(req->smb_conn, + "LANMAN2 does not support SMB signing, and it is mandatory\n"); + return; + } + + smbsrv_send_reply(req); +} + +static void reply_nt1_orig(struct smbsrv_request *req) +{ + /* Create a token value and add it to the outgoing packet. */ + if (req->smb_conn->negotiate.encrypted_passwords) { + req_grow_data(req, 8); + /* note that we do not send a challenge at all if + we are using plaintext */ + get_challenge(req->smb_conn, req->out.ptr); + req->out.ptr += 8; + SCVAL(req->out.vwv+1, VWV(16), 8); + } + req_push_str(req, NULL, lpcfg_workgroup(req->smb_conn->lp_ctx), -1, STR_UNICODE|STR_TERMINATE|STR_NOALIGN); + req_push_str(req, NULL, lpcfg_netbios_name(req->smb_conn->lp_ctx), -1, STR_UNICODE|STR_TERMINATE|STR_NOALIGN); + DEBUG(3,("not using extended security (SPNEGO or NTLMSSP)\n")); +} + +/* + try to determine if the filesystem supports large files +*/ +static bool large_file_support(const char *path) +{ + int fd; + ssize_t ret; + char c; + + fd = open(path, O_RDWR|O_CREAT, 0600); + unlink(path); + if (fd == -1) { + /* have to assume large files are OK */ + return true; + } + ret = pread(fd, &c, 1, ((uint64_t)1)<<32); + close(fd); + return ret == 0; +} + +/**************************************************************************** + Reply for the nt protocol. +****************************************************************************/ +static void reply_nt1(struct smbsrv_request *req, uint16_t choice) +{ + /* dual names + lock_and_read + nt SMBs + remote API calls */ + int capabilities; + int secword=0; + time_t t = req->request_time.tv_sec; + NTTIME nttime; + bool negotiate_spnego = false; + char *large_test_path; + + unix_to_nt_time(&nttime, t); + + capabilities = + CAP_NT_FIND | CAP_LOCK_AND_READ | + CAP_LEVEL_II_OPLOCKS | CAP_NT_SMBS | CAP_RPC_REMOTE_APIS; + + req->smb_conn->negotiate.encrypted_passwords = lpcfg_encrypt_passwords(req->smb_conn->lp_ctx); + + /* do spnego in user level security if the client + supports it and we can do encrypted passwords */ + + if (req->smb_conn->negotiate.encrypted_passwords && + (req->flags2 & FLAGS2_EXTENDED_SECURITY)) { + negotiate_spnego = true; + capabilities |= CAP_EXTENDED_SECURITY; + } + + if (lpcfg_large_readwrite(req->smb_conn->lp_ctx)) { + capabilities |= CAP_LARGE_READX | CAP_LARGE_WRITEX | CAP_W2K_SMBS; + } + + large_test_path = lpcfg_lock_path(req, req->smb_conn->lp_ctx, "large_test.dat"); + if (large_file_support(large_test_path)) { + capabilities |= CAP_LARGE_FILES; + } + + if (!lpcfg_async_smb_echo_handler(req->smb_conn->lp_ctx) && + lpcfg_read_raw(req->smb_conn->lp_ctx) && + lpcfg_write_raw(req->smb_conn->lp_ctx)) { + capabilities |= CAP_RAW_MODE; + } + + /* allow for disabling unicode */ + if (lpcfg_unicode(req->smb_conn->lp_ctx)) { + capabilities |= CAP_UNICODE; + } + + if (lpcfg_nt_status_support(req->smb_conn->lp_ctx)) { + capabilities |= CAP_STATUS32; + } + + if (lpcfg_host_msdfs(req->smb_conn->lp_ctx)) { + capabilities |= CAP_DFS; + } + + secword |= NEGOTIATE_SECURITY_USER_LEVEL; + + if (req->smb_conn->negotiate.encrypted_passwords) { + secword |= NEGOTIATE_SECURITY_CHALLENGE_RESPONSE; + } + + if (req->smb_conn->signing.allow_smb_signing) { + secword |= NEGOTIATE_SECURITY_SIGNATURES_ENABLED; + } + + if (req->smb_conn->signing.mandatory_signing) { + secword |= NEGOTIATE_SECURITY_SIGNATURES_REQUIRED; + } + + req->smb_conn->negotiate.protocol = PROTOCOL_NT1; + + smbsrv_setup_reply(req, 17, 0); + + SSVAL(req->out.vwv, VWV(0), choice); + SCVAL(req->out.vwv, VWV(1), secword); + + /* notice the strange +1 on vwv here? That's because + this is the one and only SMB packet that is malformed in + the specification - all the command words after the secword + are offset by 1 byte */ + SSVAL(req->out.vwv+1, VWV(1), lpcfg_max_mux(req->smb_conn->lp_ctx)); + SSVAL(req->out.vwv+1, VWV(2), 1); /* num vcs */ + SIVAL(req->out.vwv+1, VWV(3), req->smb_conn->negotiate.max_recv); + SIVAL(req->out.vwv+1, VWV(5), 0x10000); /* raw size. full 64k */ + SIVAL(req->out.vwv+1, VWV(7), req->smb_conn->connection->server_id.pid); /* session key */ + + SIVAL(req->out.vwv+1, VWV(9), capabilities); + push_nttime(req->out.vwv+1, VWV(11), nttime); + SSVALS(req->out.vwv+1,VWV(15), req->smb_conn->negotiate.zone_offset/60); + + if (!negotiate_spnego) { + reply_nt1_orig(req); + } else { + struct cli_credentials *server_credentials; + struct gensec_security *gensec_security; + DATA_BLOB null_data_blob = data_blob(NULL, 0); + DATA_BLOB blob = data_blob_null; + const char *oid; + NTSTATUS nt_status; + + 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 = 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; + } + + if (req->smb_conn->negotiate.auth_context) { + smbsrv_terminate_connection(req->smb_conn, "reply_nt1: is this a secondary negprot? auth_context is non-NULL!\n"); + return; + } + req->smb_conn->negotiate.server_credentials = talloc_reparent(req, req->smb_conn, server_credentials); + + oid = GENSEC_OID_SPNEGO; + nt_status = gensec_start_mech_by_oid(gensec_security, oid); + + if (NT_STATUS_IS_OK(nt_status)) { + /* Get and push the proposed OID list into the packets */ + 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(1, ("Failed to get SPNEGO to give us the first token: %s\n", nt_errstr(nt_status))); + } + } + + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + DEBUG(3,("using SPNEGO\n")); + } else { + DEBUG(5, ("Failed to start SPNEGO, falling back to NTLMSSP only: %s\n", nt_errstr(nt_status))); + oid = GENSEC_OID_NTLMSSP; + nt_status = gensec_start_mech_by_oid(gensec_security, oid); + + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(0, ("Failed to start SPNEGO as well as NTLMSSP fallback: %s\n", nt_errstr(nt_status))); + reply_nt1_orig(req); + return; + } + /* NTLMSSP is a client-first exchange */ + blob = data_blob(NULL, 0); + DEBUG(3,("using raw-NTLMSSP\n")); + } + + req->smb_conn->negotiate.oid = oid; + + req_grow_data(req, blob.length + 16); + /* a NOT very random guid, perhaps we should get it + * from the credentials (kitchen sink...) */ + memset(req->out.ptr, '\0', 16); + req->out.ptr += 16; + + memcpy(req->out.ptr, blob.data, blob.length); + SCVAL(req->out.vwv+1, VWV(16), blob.length + 16); + req->out.ptr += blob.length; + } + + smbsrv_send_reply_nosign(req); +} + +/**************************************************************************** + Reply for the SMB2 2.001 protocol +****************************************************************************/ +static void reply_smb2(struct smbsrv_request *req, uint16_t choice) +{ + struct smbsrv_connection *smb_conn = req->smb_conn; + NTSTATUS status; + + talloc_free(smb_conn->sessions.idtree_vuid); + ZERO_STRUCT(smb_conn->sessions); + talloc_free(smb_conn->smb_tcons.idtree_tid); + ZERO_STRUCT(smb_conn->smb_tcons); + ZERO_STRUCT(smb_conn->signing); + + /* reply with a SMB2 packet */ + status = smbsrv_init_smb2_connection(smb_conn); + if (!NT_STATUS_IS_OK(status)) { + smbsrv_terminate_connection(smb_conn, nt_errstr(status)); + talloc_free(req); + return; + } + packet_set_callback(smb_conn->packet, smbsrv_recv_smb2_request); + smb2srv_reply_smb_negprot(req); + req = NULL; +} + +/* List of supported protocols, most desired first */ +static const struct { + const char *proto_name; + const char *short_name; + void (*proto_reply_fn)(struct smbsrv_request *req, uint16_t choice); + int protocol_level; +} supported_protocols[] = { + {"SMB 2.002", "SMB2", reply_smb2, PROTOCOL_SMB2_02}, + {"NT LANMAN 1.0", "NT1", reply_nt1, PROTOCOL_NT1}, + {"NT LM 0.12", "NT1", reply_nt1, PROTOCOL_NT1}, + {"LANMAN2.1", "LANMAN2", reply_lanman2, PROTOCOL_LANMAN2}, + {"LM1.2X002", "LANMAN2", reply_lanman2, PROTOCOL_LANMAN2}, + {"Samba", "LANMAN2", reply_lanman2, PROTOCOL_LANMAN2}, + {"DOS LM1.2X002", "LANMAN2", reply_lanman2, PROTOCOL_LANMAN2}, + {"Windows for Workgroups 3.1a", "LANMAN1", reply_lanman1, PROTOCOL_LANMAN1}, + {"LANMAN1.0", "LANMAN1", reply_lanman1, PROTOCOL_LANMAN1}, + {"MICROSOFT NETWORKS 3.0", "LANMAN1", reply_lanman1, PROTOCOL_LANMAN1}, + {"MICROSOFT NETWORKS 1.03", "COREPLUS", reply_coreplus, PROTOCOL_COREPLUS}, + {"PC NETWORK PROGRAM 1.0", "CORE", reply_corep, PROTOCOL_CORE}, + {NULL,NULL,NULL,0}, +}; + +/**************************************************************************** + Reply to a negprot. +****************************************************************************/ + +void smbsrv_reply_negprot(struct smbsrv_request *req) +{ + int protocol; + uint8_t *p; + uint32_t protos_count = 0; + const char **protos = NULL; + + if (req->smb_conn->negotiate.done_negprot) { + smbsrv_terminate_connection(req->smb_conn, "multiple negprot's are not permitted"); + return; + } + req->smb_conn->negotiate.done_negprot = true; + + p = req->in.data; + while (true) { + size_t len; + + protos = talloc_realloc(req, protos, const char *, protos_count + 1); + if (!protos) { + smbsrv_terminate_connection(req->smb_conn, nt_errstr(NT_STATUS_NO_MEMORY)); + return; + } + protos[protos_count] = NULL; + len = req_pull_ascii4(&req->in.bufinfo, &protos[protos_count], p, STR_ASCII|STR_TERMINATE); + p += len; + if (len == 0 || !protos[protos_count]) break; + + DEBUG(5,("Requested protocol [%d][%s]\n", protos_count, protos[protos_count])); + protos_count++; + } + + /* Check for protocols, most desirable first */ + for (protocol = 0; supported_protocols[protocol].proto_name; protocol++) { + int i; + + if (supported_protocols[protocol].protocol_level > lpcfg_server_max_protocol(req->smb_conn->lp_ctx)) + continue; + if (supported_protocols[protocol].protocol_level < lpcfg_server_min_protocol(req->smb_conn->lp_ctx)) + continue; + + for (i = 0; i < protos_count; i++) { + if (strcmp(supported_protocols[protocol].proto_name, protos[i]) != 0) continue; + + supported_protocols[protocol].proto_reply_fn(req, i); + DEBUG(3,("Selected protocol [%d][%s]\n", + i, supported_protocols[protocol].proto_name)); + return; + } + } + + smbsrv_terminate_connection(req->smb_conn, "No protocol supported !"); +} diff --git a/source4/smb_server/smb/nttrans.c b/source4/smb_server/smb/nttrans.c new file mode 100644 index 0000000..8e4d004 --- /dev/null +++ b/source4/smb_server/smb/nttrans.c @@ -0,0 +1,815 @@ +/* + Unix SMB/CIFS implementation. + NT transaction handling + Copyright (C) Andrew Tridgell 2003 + Copyright (C) James J Myers 2003 <myersjj@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +/* + This file handles the parsing of transact2 requests +*/ + +#include "includes.h" +#include "smb_server/smb_server.h" +#include "ntvfs/ntvfs.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "librpc/gen_ndr/ndr_security.h" + +/* + hold the state of a nttrans op while in progress. Needed to allow for async backend + functions. +*/ +struct nttrans_op { + struct smb_nttrans *trans; + NTSTATUS (*send_fn)(struct nttrans_op *); + void *op_info; +}; + + +/* setup a nttrans reply, given the data and params sizes */ +static NTSTATUS nttrans_setup_reply(struct nttrans_op *op, + struct smb_nttrans *trans, + uint32_t param_size, uint32_t data_size, + uint8_t setup_count) +{ + trans->out.setup_count = setup_count; + if (setup_count != 0) { + trans->out.setup = talloc_zero_array(op, uint8_t, setup_count*2); + NT_STATUS_HAVE_NO_MEMORY(trans->out.setup); + } + trans->out.params = data_blob_talloc(op, NULL, param_size); + if (param_size != 0) { + NT_STATUS_HAVE_NO_MEMORY(trans->out.params.data); + } + trans->out.data = data_blob_talloc(op, NULL, data_size); + if (data_size != 0) { + NT_STATUS_HAVE_NO_MEMORY(trans->out.data.data); + } + return NT_STATUS_OK; +} + +/* + send a nttrans create reply +*/ +static NTSTATUS nttrans_create_send(struct nttrans_op *op) +{ + union smb_open *io = talloc_get_type(op->op_info, union smb_open); + uint8_t *params; + NTSTATUS status; + + status = nttrans_setup_reply(op, op->trans, 69, 0, 0); + NT_STATUS_NOT_OK_RETURN(status); + params = op->trans->out.params.data; + + SSVAL(params, 0, io->ntcreatex.out.oplock_level); + smbsrv_push_fnum(params, 2, io->ntcreatex.out.file.ntvfs); + SIVAL(params, 4, io->ntcreatex.out.create_action); + SIVAL(params, 8, 0); /* ea error offset */ + push_nttime(params, 12, io->ntcreatex.out.create_time); + push_nttime(params, 20, io->ntcreatex.out.access_time); + push_nttime(params, 28, io->ntcreatex.out.write_time); + push_nttime(params, 36, io->ntcreatex.out.change_time); + SIVAL(params, 44, io->ntcreatex.out.attrib); + SBVAL(params, 48, io->ntcreatex.out.alloc_size); + SBVAL(params, 56, io->ntcreatex.out.size); + SSVAL(params, 64, io->ntcreatex.out.file_type); + SSVAL(params, 66, io->ntcreatex.out.ipc_state); + SCVAL(params, 68, io->ntcreatex.out.is_directory); + + return NT_STATUS_OK; +} + +/* + parse NTTRANS_CREATE request + */ +static NTSTATUS nttrans_create(struct smbsrv_request *req, + struct nttrans_op *op) +{ + struct smb_nttrans *trans = op->trans; + union smb_open *io; + uint16_t fname_len; + uint32_t sd_length, ea_length; + NTSTATUS status; + uint8_t *params; + enum ndr_err_code ndr_err; + + if (trans->in.params.length < 54) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* parse the request */ + io = talloc(op, union smb_open); + NT_STATUS_HAVE_NO_MEMORY(io); + + io->ntcreatex.level = RAW_OPEN_NTTRANS_CREATE; + + params = trans->in.params.data; + + io->ntcreatex.in.flags = IVAL(params, 0); + io->ntcreatex.in.root_fid.ntvfs = smbsrv_pull_fnum(req, params, 4); + io->ntcreatex.in.access_mask = IVAL(params, 8); + io->ntcreatex.in.alloc_size = BVAL(params, 12); + io->ntcreatex.in.file_attr = IVAL(params, 20); + io->ntcreatex.in.share_access = IVAL(params, 24); + io->ntcreatex.in.open_disposition = IVAL(params, 28); + io->ntcreatex.in.create_options = IVAL(params, 32); + sd_length = IVAL(params, 36); + ea_length = IVAL(params, 40); + fname_len = IVAL(params, 44); + io->ntcreatex.in.impersonation = IVAL(params, 48); + io->ntcreatex.in.security_flags = CVAL(params, 52); + io->ntcreatex.in.sec_desc = NULL; + io->ntcreatex.in.ea_list = NULL; + io->ntcreatex.in.query_maximal_access = false; + io->ntcreatex.in.query_on_disk_id = false; + io->ntcreatex.in.private_flags = 0; + + req_pull_string(&req->in.bufinfo, &io->ntcreatex.in.fname, + params + 53, + MIN(fname_len+1, trans->in.params.length - 53), + STR_NO_RANGE_CHECK | STR_TERMINATE); + if (!io->ntcreatex.in.fname) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (sd_length > trans->in.data.length || + ea_length > trans->in.data.length || + (sd_length+ea_length) > trans->in.data.length) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* this call has an optional security descriptor */ + if (sd_length != 0) { + DATA_BLOB blob; + blob.data = trans->in.data.data; + blob.length = sd_length; + io->ntcreatex.in.sec_desc = talloc(io, struct security_descriptor); + if (io->ntcreatex.in.sec_desc == NULL) { + return NT_STATUS_NO_MEMORY; + } + ndr_err = ndr_pull_struct_blob(&blob, io, + io->ntcreatex.in.sec_desc, + (ndr_pull_flags_fn_t)ndr_pull_security_descriptor); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ndr_map_error2ntstatus(ndr_err); + } + } + + /* and an optional ea_list */ + if (ea_length > 4) { + DATA_BLOB blob; + blob.data = trans->in.data.data + sd_length; + blob.length = ea_length; + io->ntcreatex.in.ea_list = talloc(io, struct smb_ea_list); + if (io->ntcreatex.in.ea_list == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = ea_pull_list_chained(&blob, io, + &io->ntcreatex.in.ea_list->num_eas, + &io->ntcreatex.in.ea_list->eas); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + op->send_fn = nttrans_create_send; + op->op_info = io; + + return ntvfs_open(req->ntvfs, io); +} + + +/* + send NTTRANS_QUERY_SEC_DESC reply + */ +static NTSTATUS nttrans_query_sec_desc_send(struct nttrans_op *op) +{ + union smb_fileinfo *io = talloc_get_type(op->op_info, union smb_fileinfo); + uint8_t *params; + NTSTATUS status; + enum ndr_err_code ndr_err; + + status = nttrans_setup_reply(op, op->trans, 4, 0, 0); + NT_STATUS_NOT_OK_RETURN(status); + params = op->trans->out.params.data; + + ndr_err = ndr_push_struct_blob(&op->trans->out.data, op, + 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); + } + + SIVAL(params, 0, op->trans->out.data.length); + + return NT_STATUS_OK; +} + +/* + parse NTTRANS_QUERY_SEC_DESC request + */ +static NTSTATUS nttrans_query_sec_desc(struct smbsrv_request *req, + struct nttrans_op *op) +{ + struct smb_nttrans *trans = op->trans; + union smb_fileinfo *io; + + if (trans->in.params.length < 8) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* parse the request */ + 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 = smbsrv_pull_fnum(req, trans->in.params.data, 0); + io->query_secdesc.in.secinfo_flags = IVAL(trans->in.params.data, 4); + + op->op_info = io; + op->send_fn = nttrans_query_sec_desc_send; + + SMBSRV_CHECK_FILE_HANDLE_NTSTATUS(io->query_secdesc.in.file.ntvfs); + return ntvfs_qfileinfo(req->ntvfs, io); +} + + +/* + parse NTTRANS_SET_SEC_DESC request + */ +static NTSTATUS nttrans_set_sec_desc(struct smbsrv_request *req, + struct nttrans_op *op) +{ + struct smb_nttrans *trans = op->trans; + union smb_setfileinfo *io; + enum ndr_err_code ndr_err; + + if (trans->in.params.length < 8) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* parse the request */ + io = talloc(req, union smb_setfileinfo); + NT_STATUS_HAVE_NO_MEMORY(io); + + io->set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + io->set_secdesc.in.file.ntvfs = smbsrv_pull_fnum(req, trans->in.params.data, 0); + io->set_secdesc.in.secinfo_flags = IVAL(trans->in.params.data, 4); + + 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(&trans->in.data, req, + 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); + } + + SMBSRV_CHECK_FILE_HANDLE_NTSTATUS(io->set_secdesc.in.file.ntvfs); + return ntvfs_setfileinfo(req->ntvfs, io); +} + + +/* parse NTTRANS_RENAME request + */ +static NTSTATUS nttrans_rename(struct smbsrv_request *req, + struct nttrans_op *op) +{ + struct smb_nttrans *trans = op->trans; + union smb_rename *io; + + if (trans->in.params.length < 5) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* parse the request */ + io = talloc(req, union smb_rename); + NT_STATUS_HAVE_NO_MEMORY(io); + + io->nttrans.level = RAW_RENAME_NTTRANS; + io->nttrans.in.file.ntvfs = smbsrv_pull_fnum(req, trans->in.params.data, 0); + io->nttrans.in.flags = SVAL(trans->in.params.data, 2); + + smbsrv_blob_pull_string(&req->in.bufinfo, &trans->in.params, 4, + &io->nttrans.in.new_name, + STR_TERMINATE); + if (!io->nttrans.in.new_name) { + return NT_STATUS_INVALID_PARAMETER; + } + + SMBSRV_CHECK_FILE_HANDLE_NTSTATUS(io->nttrans.in.file.ntvfs); + return ntvfs_rename(req->ntvfs, io); +} + +/* + parse NTTRANS_IOCTL send + */ +static NTSTATUS nttrans_ioctl_send(struct nttrans_op *op) +{ + union smb_ioctl *info = talloc_get_type(op->op_info, union smb_ioctl); + NTSTATUS status; + + /* + * we pass 0 as data_count here, + * because we reuse the DATA_BLOB from the smb_ioctl + * struct + */ + status = nttrans_setup_reply(op, op->trans, 0, 0, 1); + NT_STATUS_NOT_OK_RETURN(status); + + op->trans->out.setup[0] = 0; + op->trans->out.data = info->ntioctl.out.blob; + + return NT_STATUS_OK; +} + + +/* + parse NTTRANS_IOCTL request + */ +static NTSTATUS nttrans_ioctl(struct smbsrv_request *req, + struct nttrans_op *op) +{ + struct smb_nttrans *trans = op->trans; + union smb_ioctl *nt; + + /* should have at least 4 setup words */ + if (trans->in.setup_count != 4) { + return NT_STATUS_INVALID_PARAMETER; + } + + nt = talloc(op, union smb_ioctl); + NT_STATUS_HAVE_NO_MEMORY(nt); + + nt->ntioctl.level = RAW_IOCTL_NTIOCTL; + nt->ntioctl.in.function = IVAL(trans->in.setup, 0); + nt->ntioctl.in.file.ntvfs = smbsrv_pull_fnum(req, (uint8_t *)trans->in.setup, 4); + nt->ntioctl.in.fsctl = CVAL(trans->in.setup, 6); + nt->ntioctl.in.filter = CVAL(trans->in.setup, 7); + nt->ntioctl.in.max_data = trans->in.max_data; + nt->ntioctl.in.blob = trans->in.data; + + op->op_info = nt; + op->send_fn = nttrans_ioctl_send; + + SMBSRV_CHECK_FILE_HANDLE_NTSTATUS(nt->ntioctl.in.file.ntvfs); + return ntvfs_ioctl(req->ntvfs, nt); +} + + +/* + send NTTRANS_NOTIFY_CHANGE reply + */ +static NTSTATUS nttrans_notify_change_send(struct nttrans_op *op) +{ + union smb_notify *info = talloc_get_type(op->op_info, union smb_notify); + size_t size = 0; + int i; + NTSTATUS status; + uint8_t *p; +#define MAX_BYTES_PER_CHAR 3 + + /* work out how big the reply buffer could be */ + for (i=0;i<info->nttrans.out.num_changes;i++) { + size += 12 + 3 + (1+strlen(info->nttrans.out.changes[i].name.s)) * MAX_BYTES_PER_CHAR; + } + + status = nttrans_setup_reply(op, op->trans, size, 0, 0); + NT_STATUS_NOT_OK_RETURN(status); + p = op->trans->out.params.data; + + /* construct the changes buffer */ + for (i=0;i<info->nttrans.out.num_changes;i++) { + uint32_t ofs; + ssize_t len; + + SIVAL(p, 4, info->nttrans.out.changes[i].action); + len = push_string(p + 12, info->nttrans.out.changes[i].name.s, + op->trans->out.params.length - + (p+12 - op->trans->out.params.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 == info->nttrans.out.num_changes-1) { + SIVAL(p, 0, 0); + } else { + SIVAL(p, 0, ofs); + } + + p += ofs; + } + + op->trans->out.params.length = p - op->trans->out.params.data; + + return NT_STATUS_OK; +} + +/* + parse NTTRANS_NOTIFY_CHANGE request + */ +static NTSTATUS nttrans_notify_change(struct smbsrv_request *req, + struct nttrans_op *op) +{ + struct smb_nttrans *trans = op->trans; + union smb_notify *info; + + /* should have at least 4 setup words */ + if (trans->in.setup_count != 4) { + return NT_STATUS_INVALID_PARAMETER; + } + + info = talloc(op, union smb_notify); + NT_STATUS_HAVE_NO_MEMORY(info); + + info->nttrans.level = RAW_NOTIFY_NTTRANS; + info->nttrans.in.completion_filter = IVAL(trans->in.setup, 0); + info->nttrans.in.file.ntvfs = smbsrv_pull_fnum(req, (uint8_t *)trans->in.setup, 4); + info->nttrans.in.recursive = SVAL(trans->in.setup, 6); + info->nttrans.in.buffer_size = trans->in.max_param; + + op->op_info = info; + op->send_fn = nttrans_notify_change_send; + + SMBSRV_CHECK_FILE_HANDLE_NTSTATUS(info->nttrans.in.file.ntvfs); + return ntvfs_notify(req->ntvfs, info); +} + +/* + backend for nttrans requests +*/ +static NTSTATUS nttrans_backend(struct smbsrv_request *req, + struct nttrans_op *op) +{ + /* the nttrans command is in function */ + switch (op->trans->in.function) { + case NT_TRANSACT_CREATE: + return nttrans_create(req, op); + case NT_TRANSACT_IOCTL: + return nttrans_ioctl(req, op); + case NT_TRANSACT_RENAME: + return nttrans_rename(req, op); + case NT_TRANSACT_QUERY_SECURITY_DESC: + return nttrans_query_sec_desc(req, op); + case NT_TRANSACT_SET_SECURITY_DESC: + return nttrans_set_sec_desc(req, op); + case NT_TRANSACT_NOTIFY_CHANGE: + return nttrans_notify_change(req, op); + } + + /* an unknown nttrans command */ + return NT_STATUS_DOS(ERRSRV, ERRerror); +} + + +static void reply_nttrans_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + uint32_t params_left, data_left; + uint8_t *params, *data; + struct smb_nttrans *trans; + struct nttrans_op *op; + + SMBSRV_CHECK_ASYNC_STATUS(op, struct nttrans_op); + + trans = op->trans; + + /* if this function needs work to form the nttrans reply buffer, then + call that now */ + if (op->send_fn != NULL) { + NTSTATUS status; + status = op->send_fn(op); + if (!NT_STATUS_IS_OK(status)) { + smbsrv_send_error(req, status); + return; + } + } + + smbsrv_setup_reply(req, 18 + trans->out.setup_count, 0); + + /* note that we don't check the max_setup count (matching w2k3 + behaviour) */ + + if (trans->out.params.length > trans->in.max_param) { + smbsrv_setup_error(req, NT_STATUS_BUFFER_TOO_SMALL); + trans->out.params.length = trans->in.max_param; + } + if (trans->out.data.length > trans->in.max_data) { + smbsrv_setup_error(req, NT_STATUS_BUFFER_TOO_SMALL); + trans->out.data.length = trans->in.max_data; + } + + params_left = trans->out.params.length; + data_left = trans->out.data.length; + params = trans->out.params.data; + data = trans->out.data.data; + + /* we need to divide up the reply into chunks that fit into + the negotiated buffer size */ + do { + uint32_t this_data, this_param, max_bytes; + unsigned int align1 = 1, align2 = (params_left ? 2 : 0); + struct smbsrv_request *this_req; + + max_bytes = req_max_data(req) - (align1 + align2); + + this_param = params_left; + if (this_param > max_bytes) { + this_param = max_bytes; + } + max_bytes -= this_param; + + this_data = data_left; + if (this_data > max_bytes) { + this_data = max_bytes; + } + + /* don't destroy unless this is the last chunk */ + if (params_left - this_param != 0 || + data_left - this_data != 0) { + this_req = smbsrv_setup_secondary_request(req); + } else { + this_req = req; + } + + req_grow_data(this_req, this_param + this_data + (align1 + align2)); + + SSVAL(this_req->out.vwv, 0, 0); /* reserved */ + SCVAL(this_req->out.vwv, 2, 0); /* reserved */ + SIVAL(this_req->out.vwv, 3, trans->out.params.length); + SIVAL(this_req->out.vwv, 7, trans->out.data.length); + + SIVAL(this_req->out.vwv, 11, this_param); + SIVAL(this_req->out.vwv, 15, align1 + PTR_DIFF(this_req->out.data, this_req->out.hdr)); + SIVAL(this_req->out.vwv, 19, PTR_DIFF(params, trans->out.params.data)); + + SIVAL(this_req->out.vwv, 23, this_data); + SIVAL(this_req->out.vwv, 27, align1 + align2 + + PTR_DIFF(this_req->out.data + this_param, this_req->out.hdr)); + SIVAL(this_req->out.vwv, 31, PTR_DIFF(data, trans->out.data.data)); + + SCVAL(this_req->out.vwv, 35, trans->out.setup_count); + if (trans->out.setup_count > 0) { + memcpy((char *)(this_req->out.vwv) + VWV(18), + trans->out.setup, + sizeof(uint16_t) * trans->out.setup_count); + } + memset(this_req->out.data, 0, align1); + if (this_param != 0) { + memcpy(this_req->out.data + align1, params, this_param); + } + memset(this_req->out.data+this_param+align1, 0, align2); + if (this_data != 0) { + memcpy(this_req->out.data+this_param+align1+align2, + data, this_data); + } + + params_left -= this_param; + data_left -= this_data; + params += this_param; + data += this_data; + + smbsrv_send_reply(this_req); + } while (params_left != 0 || data_left != 0); +} + +/* + send a continue request +*/ +static void reply_nttrans_continue(struct smbsrv_request *req, struct smb_nttrans *trans) +{ + struct smbsrv_request *req2; + struct smbsrv_trans_partial *tp; + int count; + + /* make sure they don't flood us */ + for (count=0,tp=req->smb_conn->trans_partial;tp;tp=tp->next) count++; + if (count > 100) { + smbsrv_send_error(req, NT_STATUS_INSUFFICIENT_RESOURCES); + return; + } + + tp = talloc(req, struct smbsrv_trans_partial); + + tp->req = req; + tp->u.nttrans = trans; + tp->command = SMBnttrans; + + DLIST_ADD(req->smb_conn->trans_partial, tp); + talloc_set_destructor(tp, smbsrv_trans_partial_destructor); + + req2 = smbsrv_setup_secondary_request(req); + + /* send a 'please continue' reply */ + smbsrv_setup_reply(req2, 0, 0); + smbsrv_send_reply(req2); +} + + +/* + answer a reconstructed trans request +*/ +static void reply_nttrans_complete(struct smbsrv_request *req, struct smb_nttrans *trans) +{ + struct nttrans_op *op; + + SMBSRV_TALLOC_IO_PTR(op, struct nttrans_op); + SMBSRV_SETUP_NTVFS_REQUEST(reply_nttrans_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + op->trans = trans; + op->op_info = NULL; + op->send_fn = NULL; + + /* its a full request, give it to the backend */ + ZERO_STRUCT(trans->out); + SMBSRV_CALL_NTVFS_BACKEND(nttrans_backend(req, op)); +} + + +/**************************************************************************** + Reply to an SMBnttrans request +****************************************************************************/ +void smbsrv_reply_nttrans(struct smbsrv_request *req) +{ + struct smb_nttrans *trans; + uint32_t param_ofs, data_ofs; + uint32_t param_count, data_count; + uint32_t param_total, data_total; + + /* parse request */ + if (req->in.wct < 19) { + smbsrv_send_error(req, NT_STATUS_FOOBAR); + return; + } + + trans = talloc(req, struct smb_nttrans); + if (trans == NULL) { + smbsrv_send_error(req, NT_STATUS_NO_MEMORY); + return; + } + + trans->in.max_setup = CVAL(req->in.vwv, 0); + param_total = IVAL(req->in.vwv, 3); + data_total = IVAL(req->in.vwv, 7); + trans->in.max_param = IVAL(req->in.vwv, 11); + trans->in.max_data = IVAL(req->in.vwv, 15); + param_count = IVAL(req->in.vwv, 19); + param_ofs = IVAL(req->in.vwv, 23); + data_count = IVAL(req->in.vwv, 27); + data_ofs = IVAL(req->in.vwv, 31); + trans->in.setup_count= CVAL(req->in.vwv, 35); + trans->in.function = SVAL(req->in.vwv, 36); + + if (req->in.wct != 19 + trans->in.setup_count) { + smbsrv_send_error(req, NT_STATUS_DOS(ERRSRV, ERRerror)); + return; + } + + /* parse out the setup words */ + trans->in.setup = talloc_array(req, uint8_t, trans->in.setup_count*2); + if (!trans->in.setup) { + smbsrv_send_error(req, NT_STATUS_NO_MEMORY); + return; + } + memcpy(trans->in.setup, (char *)(req->in.vwv) + VWV(19), + sizeof(uint16_t) * trans->in.setup_count); + + if (!req_pull_blob(&req->in.bufinfo, req->in.hdr + param_ofs, param_count, &trans->in.params) || + !req_pull_blob(&req->in.bufinfo, req->in.hdr + data_ofs, data_count, &trans->in.data)) { + smbsrv_send_error(req, NT_STATUS_FOOBAR); + return; + } + + /* is it a partial request? if so, then send a 'send more' message */ + if (param_total > param_count || data_total > data_count) { + reply_nttrans_continue(req, trans); + return; + } + + reply_nttrans_complete(req, trans); +} + + +/**************************************************************************** + Reply to an SMBnttranss request +****************************************************************************/ +void smbsrv_reply_nttranss(struct smbsrv_request *req) +{ + struct smbsrv_trans_partial *tp; + struct smb_nttrans *trans = NULL; + uint32_t param_ofs, data_ofs; + uint32_t param_count, data_count; + uint32_t param_disp, data_disp; + uint32_t param_total, data_total; + DATA_BLOB params, data; + + /* parse request */ + if (req->in.wct != 18) { + smbsrv_send_error(req, NT_STATUS_DOS(ERRSRV, ERRerror)); + return; + } + + for (tp=req->smb_conn->trans_partial;tp;tp=tp->next) { + if (tp->command == SMBnttrans && + SVAL(tp->req->in.hdr, HDR_MID) == SVAL(req->in.hdr, HDR_MID)) { +/* TODO: check the VUID, PID and TID too? */ + break; + } + } + + if (tp == NULL) { + smbsrv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + + trans = tp->u.nttrans; + + param_total = IVAL(req->in.vwv, 3); + data_total = IVAL(req->in.vwv, 7); + param_count = IVAL(req->in.vwv, 11); + param_ofs = IVAL(req->in.vwv, 15); + param_disp = IVAL(req->in.vwv, 19); + data_count = IVAL(req->in.vwv, 23); + data_ofs = IVAL(req->in.vwv, 27); + data_disp = IVAL(req->in.vwv, 31); + + if (!req_pull_blob(&req->in.bufinfo, req->in.hdr + param_ofs, param_count, ¶ms) || + !req_pull_blob(&req->in.bufinfo, req->in.hdr + data_ofs, data_count, &data)) { + smbsrv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + + /* only allow contiguous requests */ + if ((param_count != 0 && + param_disp != trans->in.params.length) || + (data_count != 0 && + data_disp != trans->in.data.length)) { + smbsrv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + + /* add to the existing request */ + if (param_count != 0) { + trans->in.params.data = talloc_realloc(trans, + trans->in.params.data, + uint8_t, + param_disp + param_count); + if (trans->in.params.data == NULL) { + smbsrv_send_error(tp->req, NT_STATUS_NO_MEMORY); + return; + } + trans->in.params.length = param_disp + param_count; + } + + if (data_count != 0) { + trans->in.data.data = talloc_realloc(trans, + trans->in.data.data, + uint8_t, + data_disp + data_count); + if (trans->in.data.data == NULL) { + smbsrv_send_error(tp->req, NT_STATUS_NO_MEMORY); + return; + } + trans->in.data.length = data_disp + data_count; + } + + memcpy(trans->in.params.data + param_disp, params.data, params.length); + memcpy(trans->in.data.data + data_disp, data.data, data.length); + + /* the sequence number of the reply is taken from the last secondary + response */ + tp->req->seq_num = req->seq_num; + + /* we don't reply to Transs2 requests */ + talloc_free(req); + + if (trans->in.params.length == param_total && + trans->in.data.length == data_total) { + /* its now complete */ + req = tp->req; + talloc_free(tp); + reply_nttrans_complete(req, trans); + } + return; +} diff --git a/source4/smb_server/smb/receive.c b/source4/smb_server/smb/receive.c new file mode 100644 index 0000000..b96cdbd --- /dev/null +++ b/source4/smb_server/smb/receive.c @@ -0,0 +1,679 @@ +/* + Unix SMB/CIFS implementation. + process incoming packets - main loop + Copyright (C) Andrew Tridgell 1992-2005 + Copyright (C) James J Myers 2003 <myersjj@samba.org> + Copyright (C) Stefan Metzmacher 2004-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 "lib/util/server_id.h" +#include "samba/service_stream.h" +#include "smb_server/smb_server.h" +#include "system/filesys.h" +#include "param/param.h" +#include "cluster/cluster.h" + +/* + send an oplock break request to a client +*/ +NTSTATUS smbsrv_send_oplock_break(void *p, struct ntvfs_handle *ntvfs, uint8_t level) +{ + struct smbsrv_tcon *tcon = talloc_get_type(p, struct smbsrv_tcon); + struct smbsrv_request *req; + + req = smbsrv_init_request(tcon->smb_conn); + NT_STATUS_HAVE_NO_MEMORY(req); + + smbsrv_setup_reply(req, 8, 0); + + SCVAL(req->out.hdr,HDR_COM,SMBlockingX); + SSVAL(req->out.hdr,HDR_TID,tcon->tid); + SSVAL(req->out.hdr,HDR_PID,0xFFFF); + SSVAL(req->out.hdr,HDR_UID,0); + SSVAL(req->out.hdr,HDR_MID,0xFFFF); + SCVAL(req->out.hdr,HDR_FLG,0); + SSVAL(req->out.hdr,HDR_FLG2,0); + + SSVAL(req->out.vwv, VWV(0), SMB_CHAIN_NONE); + SSVAL(req->out.vwv, VWV(1), 0); + smbsrv_push_fnum(req->out.vwv, VWV(2), ntvfs); + SCVAL(req->out.vwv, VWV(3), LOCKING_ANDX_OPLOCK_RELEASE); + SCVAL(req->out.vwv, VWV(3)+1, level); + SIVAL(req->out.vwv, VWV(4), 0); + SSVAL(req->out.vwv, VWV(6), 0); + SSVAL(req->out.vwv, VWV(7), 0); + + smbsrv_send_reply(req); + return NT_STATUS_OK; +} + +static void switch_message(int type, struct smbsrv_request *req); + +/* + These flags determine some of the permissions required to do an operation +*/ +#define NEED_SESS (1<<0) +#define NEED_TCON (1<<1) +#define SIGNING_NO_REPLY (1<<2) +/* does VWV(0) of the request hold chaining information */ +#define AND_X (1<<3) +/* The 64Kb question: are requests > 64K valid? */ +#define LARGE_REQUEST (1<<4) + +/* + define a list of possible SMB messages and their corresponding + functions. Any message that has a NULL function is unimplemented - + please feel free to contribute implementations! +*/ +static const struct smb_message_struct +{ + const char *name; + void (*fn)(struct smbsrv_request *); +#define message_flags(type) smb_messages[(type) & 0xff].flags + int flags; +} + smb_messages[256] = { +/* 0x00 */ { "SMBmkdir", smbsrv_reply_mkdir, NEED_SESS|NEED_TCON }, +/* 0x01 */ { "SMBrmdir", smbsrv_reply_rmdir, NEED_SESS|NEED_TCON }, +/* 0x02 */ { "SMBopen", smbsrv_reply_open, NEED_SESS|NEED_TCON }, +/* 0x03 */ { "SMBcreate", smbsrv_reply_mknew, NEED_SESS|NEED_TCON }, +/* 0x04 */ { "SMBclose", smbsrv_reply_close, NEED_SESS|NEED_TCON }, +/* 0x05 */ { "SMBflush", smbsrv_reply_flush, NEED_SESS|NEED_TCON }, +/* 0x06 */ { "SMBunlink", smbsrv_reply_unlink, NEED_SESS|NEED_TCON }, +/* 0x07 */ { "SMBmv", smbsrv_reply_mv, NEED_SESS|NEED_TCON }, +/* 0x08 */ { "SMBgetatr", smbsrv_reply_getatr, NEED_SESS|NEED_TCON }, +/* 0x09 */ { "SMBsetatr", smbsrv_reply_setatr, NEED_SESS|NEED_TCON }, +/* 0x0a */ { "SMBread", smbsrv_reply_read, NEED_SESS|NEED_TCON }, +/* 0x0b */ { "SMBwrite", smbsrv_reply_write, NEED_SESS|NEED_TCON }, +/* 0x0c */ { "SMBlock", smbsrv_reply_lock, NEED_SESS|NEED_TCON }, +/* 0x0d */ { "SMBunlock", smbsrv_reply_unlock, NEED_SESS|NEED_TCON }, +/* 0x0e */ { "SMBctemp", smbsrv_reply_ctemp, NEED_SESS|NEED_TCON }, +/* 0x0f */ { "SMBmknew", smbsrv_reply_mknew, NEED_SESS|NEED_TCON }, +/* 0x10 */ { "SMBcheckpath", smbsrv_reply_chkpth, NEED_SESS|NEED_TCON }, +/* 0x11 */ { "SMBexit", smbsrv_reply_exit, NEED_SESS }, +/* 0x12 */ { "SMBlseek", smbsrv_reply_lseek, NEED_SESS|NEED_TCON }, +/* 0x13 */ { "SMBlockread", smbsrv_reply_lockread, NEED_SESS|NEED_TCON }, +/* 0x14 */ { "SMBwriteunlock", smbsrv_reply_writeunlock, NEED_SESS|NEED_TCON }, +/* 0x15 */ { NULL, NULL, 0 }, +/* 0x16 */ { NULL, NULL, 0 }, +/* 0x17 */ { NULL, NULL, 0 }, +/* 0x18 */ { NULL, NULL, 0 }, +/* 0x19 */ { NULL, NULL, 0 }, +/* 0x1a */ { "SMBreadbraw", smbsrv_reply_readbraw, NEED_SESS|NEED_TCON }, +/* 0x1b */ { "SMBreadBmpx", smbsrv_reply_readbmpx, NEED_SESS|NEED_TCON }, +/* 0x1c */ { "SMBreadBs", NULL, 0 }, +/* 0x1d */ { "SMBwritebraw", smbsrv_reply_writebraw, NEED_SESS|NEED_TCON }, +/* 0x1e */ { "SMBwriteBmpx", smbsrv_reply_writebmpx, NEED_SESS|NEED_TCON }, +/* 0x1f */ { "SMBwriteBs", smbsrv_reply_writebs, NEED_SESS|NEED_TCON }, +/* 0x20 */ { "SMBwritec", NULL, 0 }, +/* 0x21 */ { NULL, NULL, 0 }, +/* 0x22 */ { "SMBsetattrE", smbsrv_reply_setattrE, NEED_SESS|NEED_TCON }, +/* 0x23 */ { "SMBgetattrE", smbsrv_reply_getattrE, NEED_SESS|NEED_TCON }, +/* 0x24 */ { "SMBlockingX", smbsrv_reply_lockingX, NEED_SESS|NEED_TCON|AND_X }, +/* 0x25 */ { "SMBtrans", smbsrv_reply_trans, NEED_SESS|NEED_TCON }, +/* 0x26 */ { "SMBtranss", smbsrv_reply_transs, NEED_SESS|NEED_TCON }, +/* 0x27 */ { "SMBioctl", smbsrv_reply_ioctl, NEED_SESS|NEED_TCON }, +/* 0x28 */ { "SMBioctls", NULL, NEED_SESS|NEED_TCON }, +/* 0x29 */ { "SMBcopy", smbsrv_reply_copy, NEED_SESS|NEED_TCON }, +/* 0x2a */ { "SMBmove", NULL, NEED_SESS|NEED_TCON }, +/* 0x2b */ { "SMBecho", smbsrv_reply_echo, 0 }, +/* 0x2c */ { "SMBwriteclose", smbsrv_reply_writeclose, NEED_SESS|NEED_TCON }, +/* 0x2d */ { "SMBopenX", smbsrv_reply_open_and_X, NEED_SESS|NEED_TCON|AND_X }, +/* 0x2e */ { "SMBreadX", smbsrv_reply_read_and_X, NEED_SESS|NEED_TCON|AND_X }, +/* 0x2f */ { "SMBwriteX", smbsrv_reply_write_and_X, NEED_SESS|NEED_TCON|AND_X|LARGE_REQUEST}, +/* 0x30 */ { NULL, NULL, 0 }, +/* 0x31 */ { NULL, NULL, 0 }, +/* 0x32 */ { "SMBtrans2", smbsrv_reply_trans2, NEED_SESS|NEED_TCON }, +/* 0x33 */ { "SMBtranss2", smbsrv_reply_transs2, NEED_SESS|NEED_TCON }, +/* 0x34 */ { "SMBfindclose", smbsrv_reply_findclose, NEED_SESS|NEED_TCON }, +/* 0x35 */ { "SMBfindnclose", smbsrv_reply_findnclose, NEED_SESS|NEED_TCON }, +/* 0x36 */ { NULL, NULL, 0 }, +/* 0x37 */ { NULL, NULL, 0 }, +/* 0x38 */ { NULL, NULL, 0 }, +/* 0x39 */ { NULL, NULL, 0 }, +/* 0x3a */ { NULL, NULL, 0 }, +/* 0x3b */ { NULL, NULL, 0 }, +/* 0x3c */ { NULL, NULL, 0 }, +/* 0x3d */ { NULL, NULL, 0 }, +/* 0x3e */ { NULL, NULL, 0 }, +/* 0x3f */ { NULL, NULL, 0 }, +/* 0x40 */ { NULL, NULL, 0 }, +/* 0x41 */ { NULL, NULL, 0 }, +/* 0x42 */ { NULL, NULL, 0 }, +/* 0x43 */ { NULL, NULL, 0 }, +/* 0x44 */ { NULL, NULL, 0 }, +/* 0x45 */ { NULL, NULL, 0 }, +/* 0x46 */ { NULL, NULL, 0 }, +/* 0x47 */ { NULL, NULL, 0 }, +/* 0x48 */ { NULL, NULL, 0 }, +/* 0x49 */ { NULL, NULL, 0 }, +/* 0x4a */ { NULL, NULL, 0 }, +/* 0x4b */ { NULL, NULL, 0 }, +/* 0x4c */ { NULL, NULL, 0 }, +/* 0x4d */ { NULL, NULL, 0 }, +/* 0x4e */ { NULL, NULL, 0 }, +/* 0x4f */ { NULL, NULL, 0 }, +/* 0x50 */ { NULL, NULL, 0 }, +/* 0x51 */ { NULL, NULL, 0 }, +/* 0x52 */ { NULL, NULL, 0 }, +/* 0x53 */ { NULL, NULL, 0 }, +/* 0x54 */ { NULL, NULL, 0 }, +/* 0x55 */ { NULL, NULL, 0 }, +/* 0x56 */ { NULL, NULL, 0 }, +/* 0x57 */ { NULL, NULL, 0 }, +/* 0x58 */ { NULL, NULL, 0 }, +/* 0x59 */ { NULL, NULL, 0 }, +/* 0x5a */ { NULL, NULL, 0 }, +/* 0x5b */ { NULL, NULL, 0 }, +/* 0x5c */ { NULL, NULL, 0 }, +/* 0x5d */ { NULL, NULL, 0 }, +/* 0x5e */ { NULL, NULL, 0 }, +/* 0x5f */ { NULL, NULL, 0 }, +/* 0x60 */ { NULL, NULL, 0 }, +/* 0x61 */ { NULL, NULL, 0 }, +/* 0x62 */ { NULL, NULL, 0 }, +/* 0x63 */ { NULL, NULL, 0 }, +/* 0x64 */ { NULL, NULL, 0 }, +/* 0x65 */ { NULL, NULL, 0 }, +/* 0x66 */ { NULL, NULL, 0 }, +/* 0x67 */ { NULL, NULL, 0 }, +/* 0x68 */ { NULL, NULL, 0 }, +/* 0x69 */ { NULL, NULL, 0 }, +/* 0x6a */ { NULL, NULL, 0 }, +/* 0x6b */ { NULL, NULL, 0 }, +/* 0x6c */ { NULL, NULL, 0 }, +/* 0x6d */ { NULL, NULL, 0 }, +/* 0x6e */ { NULL, NULL, 0 }, +/* 0x6f */ { NULL, NULL, 0 }, +/* 0x70 */ { "SMBtcon", smbsrv_reply_tcon, NEED_SESS }, +/* 0x71 */ { "SMBtdis", smbsrv_reply_tdis, NEED_TCON }, +/* 0x72 */ { "SMBnegprot", smbsrv_reply_negprot, 0 }, +/* 0x73 */ { "SMBsesssetupX", smbsrv_reply_sesssetup, AND_X }, +/* 0x74 */ { "SMBulogoffX", smbsrv_reply_ulogoffX, NEED_SESS|AND_X }, /* ulogoff doesn't give a valid TID */ +/* 0x75 */ { "SMBtconX", smbsrv_reply_tcon_and_X, NEED_SESS|AND_X }, +/* 0x76 */ { NULL, NULL, 0 }, +/* 0x77 */ { NULL, NULL, 0 }, +/* 0x78 */ { NULL, NULL, 0 }, +/* 0x79 */ { NULL, NULL, 0 }, +/* 0x7a */ { NULL, NULL, 0 }, +/* 0x7b */ { NULL, NULL, 0 }, +/* 0x7c */ { NULL, NULL, 0 }, +/* 0x7d */ { NULL, NULL, 0 }, +/* 0x7e */ { NULL, NULL, 0 }, +/* 0x7f */ { NULL, NULL, 0 }, +/* 0x80 */ { "SMBdskattr", smbsrv_reply_dskattr, NEED_SESS|NEED_TCON }, +/* 0x81 */ { "SMBsearch", smbsrv_reply_search, NEED_SESS|NEED_TCON }, +/* 0x82 */ { "SMBffirst", smbsrv_reply_search, NEED_SESS|NEED_TCON }, +/* 0x83 */ { "SMBfunique", smbsrv_reply_search, NEED_SESS|NEED_TCON }, +/* 0x84 */ { "SMBfclose", smbsrv_reply_fclose, NEED_SESS|NEED_TCON }, +/* 0x85 */ { NULL, NULL, 0 }, +/* 0x86 */ { NULL, NULL, 0 }, +/* 0x87 */ { NULL, NULL, 0 }, +/* 0x88 */ { NULL, NULL, 0 }, +/* 0x89 */ { NULL, NULL, 0 }, +/* 0x8a */ { NULL, NULL, 0 }, +/* 0x8b */ { NULL, NULL, 0 }, +/* 0x8c */ { NULL, NULL, 0 }, +/* 0x8d */ { NULL, NULL, 0 }, +/* 0x8e */ { NULL, NULL, 0 }, +/* 0x8f */ { NULL, NULL, 0 }, +/* 0x90 */ { NULL, NULL, 0 }, +/* 0x91 */ { NULL, NULL, 0 }, +/* 0x92 */ { NULL, NULL, 0 }, +/* 0x93 */ { NULL, NULL, 0 }, +/* 0x94 */ { NULL, NULL, 0 }, +/* 0x95 */ { NULL, NULL, 0 }, +/* 0x96 */ { NULL, NULL, 0 }, +/* 0x97 */ { NULL, NULL, 0 }, +/* 0x98 */ { NULL, NULL, 0 }, +/* 0x99 */ { NULL, NULL, 0 }, +/* 0x9a */ { NULL, NULL, 0 }, +/* 0x9b */ { NULL, NULL, 0 }, +/* 0x9c */ { NULL, NULL, 0 }, +/* 0x9d */ { NULL, NULL, 0 }, +/* 0x9e */ { NULL, NULL, 0 }, +/* 0x9f */ { NULL, NULL, 0 }, +/* 0xa0 */ { "SMBnttrans", smbsrv_reply_nttrans, NEED_SESS|NEED_TCON|LARGE_REQUEST }, +/* 0xa1 */ { "SMBnttranss", smbsrv_reply_nttranss, NEED_SESS|NEED_TCON }, +/* 0xa2 */ { "SMBntcreateX", smbsrv_reply_ntcreate_and_X, NEED_SESS|NEED_TCON|AND_X }, +/* 0xa3 */ { NULL, NULL, 0 }, +/* 0xa4 */ { "SMBntcancel", smbsrv_reply_ntcancel, NEED_SESS|NEED_TCON|SIGNING_NO_REPLY }, +/* 0xa5 */ { "SMBntrename", smbsrv_reply_ntrename, NEED_SESS|NEED_TCON }, +/* 0xa6 */ { NULL, NULL, 0 }, +/* 0xa7 */ { NULL, NULL, 0 }, +/* 0xa8 */ { NULL, NULL, 0 }, +/* 0xa9 */ { NULL, NULL, 0 }, +/* 0xaa */ { NULL, NULL, 0 }, +/* 0xab */ { NULL, NULL, 0 }, +/* 0xac */ { NULL, NULL, 0 }, +/* 0xad */ { NULL, NULL, 0 }, +/* 0xae */ { NULL, NULL, 0 }, +/* 0xaf */ { NULL, NULL, 0 }, +/* 0xb0 */ { NULL, NULL, 0 }, +/* 0xb1 */ { NULL, NULL, 0 }, +/* 0xb2 */ { NULL, NULL, 0 }, +/* 0xb3 */ { NULL, NULL, 0 }, +/* 0xb4 */ { NULL, NULL, 0 }, +/* 0xb5 */ { NULL, NULL, 0 }, +/* 0xb6 */ { NULL, NULL, 0 }, +/* 0xb7 */ { NULL, NULL, 0 }, +/* 0xb8 */ { NULL, NULL, 0 }, +/* 0xb9 */ { NULL, NULL, 0 }, +/* 0xba */ { NULL, NULL, 0 }, +/* 0xbb */ { NULL, NULL, 0 }, +/* 0xbc */ { NULL, NULL, 0 }, +/* 0xbd */ { NULL, NULL, 0 }, +/* 0xbe */ { NULL, NULL, 0 }, +/* 0xbf */ { NULL, NULL, 0 }, +/* 0xc0 */ { "SMBsplopen", smbsrv_reply_printopen, NEED_SESS|NEED_TCON }, +/* 0xc1 */ { "SMBsplwr", smbsrv_reply_printwrite, NEED_SESS|NEED_TCON }, +/* 0xc2 */ { "SMBsplclose", smbsrv_reply_printclose, NEED_SESS|NEED_TCON }, +/* 0xc3 */ { "SMBsplretq", smbsrv_reply_printqueue, NEED_SESS|NEED_TCON }, +/* 0xc4 */ { NULL, NULL, 0 }, +/* 0xc5 */ { NULL, NULL, 0 }, +/* 0xc6 */ { NULL, NULL, 0 }, +/* 0xc7 */ { NULL, NULL, 0 }, +/* 0xc8 */ { NULL, NULL, 0 }, +/* 0xc9 */ { NULL, NULL, 0 }, +/* 0xca */ { NULL, NULL, 0 }, +/* 0xcb */ { NULL, NULL, 0 }, +/* 0xcc */ { NULL, NULL, 0 }, +/* 0xcd */ { NULL, NULL, 0 }, +/* 0xce */ { NULL, NULL, 0 }, +/* 0xcf */ { NULL, NULL, 0 }, +/* 0xd0 */ { "SMBsends", NULL, 0 }, +/* 0xd1 */ { "SMBsendb", NULL, 0 }, +/* 0xd2 */ { "SMBfwdname", NULL, 0 }, +/* 0xd3 */ { "SMBcancelf", NULL, 0 }, +/* 0xd4 */ { "SMBgetmac", NULL, 0 }, +/* 0xd5 */ { "SMBsendstrt", NULL, 0 }, +/* 0xd6 */ { "SMBsendend", NULL, 0 }, +/* 0xd7 */ { "SMBsendtxt", NULL, 0 }, +/* 0xd8 */ { NULL, NULL, 0 }, +/* 0xd9 */ { NULL, NULL, 0 }, +/* 0xda */ { NULL, NULL, 0 }, +/* 0xdb */ { NULL, NULL, 0 }, +/* 0xdc */ { NULL, NULL, 0 }, +/* 0xdd */ { NULL, NULL, 0 }, +/* 0xde */ { NULL, NULL, 0 }, +/* 0xdf */ { NULL, NULL, 0 }, +/* 0xe0 */ { NULL, NULL, 0 }, +/* 0xe1 */ { NULL, NULL, 0 }, +/* 0xe2 */ { NULL, NULL, 0 }, +/* 0xe3 */ { NULL, NULL, 0 }, +/* 0xe4 */ { NULL, NULL, 0 }, +/* 0xe5 */ { NULL, NULL, 0 }, +/* 0xe6 */ { NULL, NULL, 0 }, +/* 0xe7 */ { NULL, NULL, 0 }, +/* 0xe8 */ { NULL, NULL, 0 }, +/* 0xe9 */ { NULL, NULL, 0 }, +/* 0xea */ { NULL, NULL, 0 }, +/* 0xeb */ { NULL, NULL, 0 }, +/* 0xec */ { NULL, NULL, 0 }, +/* 0xed */ { NULL, NULL, 0 }, +/* 0xee */ { NULL, NULL, 0 }, +/* 0xef */ { NULL, NULL, 0 }, +/* 0xf0 */ { NULL, NULL, 0 }, +/* 0xf1 */ { NULL, NULL, 0 }, +/* 0xf2 */ { NULL, NULL, 0 }, +/* 0xf3 */ { NULL, NULL, 0 }, +/* 0xf4 */ { NULL, NULL, 0 }, +/* 0xf5 */ { NULL, NULL, 0 }, +/* 0xf6 */ { NULL, NULL, 0 }, +/* 0xf7 */ { NULL, NULL, 0 }, +/* 0xf8 */ { NULL, NULL, 0 }, +/* 0xf9 */ { NULL, NULL, 0 }, +/* 0xfa */ { NULL, NULL, 0 }, +/* 0xfb */ { NULL, NULL, 0 }, +/* 0xfc */ { NULL, NULL, 0 }, +/* 0xfd */ { NULL, NULL, 0 }, +/* 0xfe */ { NULL, NULL, 0 }, +/* 0xff */ { NULL, NULL, 0 } +}; + +/**************************************************************************** +receive a SMB request header from the wire, forming a request_context +from the result +****************************************************************************/ +NTSTATUS smbsrv_recv_smb_request(void *private_data, DATA_BLOB blob) +{ + struct smbsrv_connection *smb_conn = talloc_get_type(private_data, struct smbsrv_connection); + struct smbsrv_request *req; + struct timeval cur_time = timeval_current(); + uint8_t command; + + smb_conn->statistics.last_request_time = cur_time; + + /* see if its a special NBT packet */ + if (CVAL(blob.data, 0) != 0) { + req = smbsrv_init_request(smb_conn); + NT_STATUS_HAVE_NO_MEMORY(req); + + ZERO_STRUCT(req->in); + + req->in.buffer = talloc_steal(req, blob.data); + req->in.size = blob.length; + req->request_time = cur_time; + + smbsrv_reply_special(req); + return NT_STATUS_OK; + } + + if ((NBT_HDR_SIZE + MIN_SMB_SIZE) > blob.length) { + DEBUG(2,("Invalid SMB packet: length %ld\n", (long)blob.length)); + smbsrv_terminate_connection(smb_conn, "Invalid SMB packet"); + return NT_STATUS_OK; + } + + /* Make sure this is an SMB packet */ + if (IVAL(blob.data, NBT_HDR_SIZE) != SMB_MAGIC) { + DEBUG(2,("Non-SMB packet of length %ld. Terminating connection\n", + (long)blob.length)); + smbsrv_terminate_connection(smb_conn, "Non-SMB packet"); + return NT_STATUS_OK; + } + + req = smbsrv_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->chained_fnum = -1; + req->in.allocated = req->in.size; + req->in.hdr = req->in.buffer + NBT_HDR_SIZE; + req->in.vwv = req->in.hdr + HDR_VWV; + req->in.wct = CVAL(req->in.hdr, HDR_WCT); + + command = CVAL(req->in.hdr, HDR_COM); + + if (req->in.vwv + VWV(req->in.wct) <= req->in.buffer + req->in.size) { + req->in.data = req->in.vwv + VWV(req->in.wct) + 2; + req->in.data_size = SVAL(req->in.vwv, VWV(req->in.wct)); + + /* special handling for oversize calls. Windows seems + to take the maximum of the BCC value and the + computed buffer size. This handles oversized writeX + calls, and possibly oversized SMBtrans calls */ + if ((message_flags(command) & LARGE_REQUEST) && + ( !(message_flags(command) & AND_X) || + (req->in.wct < 1 || SVAL(req->in.vwv, VWV(0)) == SMB_CHAIN_NONE)) && + req->in.data_size < req->in.size - PTR_DIFF(req->in.data,req->in.buffer)) { + req->in.data_size = req->in.size - PTR_DIFF(req->in.data,req->in.buffer); + } + } + + if (NBT_HDR_SIZE + MIN_SMB_SIZE + 2*req->in.wct > req->in.size) { + DEBUG(2,("Invalid SMB word count %d\n", req->in.wct)); + smbsrv_terminate_connection(req->smb_conn, "Invalid SMB packet"); + return NT_STATUS_OK; + } + + if (NBT_HDR_SIZE + MIN_SMB_SIZE + 2*req->in.wct + req->in.data_size > req->in.size) { + DEBUG(2,("Invalid SMB buffer length count %d\n", + (int)req->in.data_size)); + smbsrv_terminate_connection(req->smb_conn, "Invalid SMB packet"); + return NT_STATUS_OK; + } + + req->flags2 = SVAL(req->in.hdr, HDR_FLG2); + + /* fix the bufinfo */ + smbsrv_setup_bufinfo(req); + + if (!smbsrv_signing_check_incoming(req)) { + smbsrv_send_error(req, NT_STATUS_ACCESS_DENIED); + return NT_STATUS_OK; + } + + command = CVAL(req->in.hdr, HDR_COM); + switch_message(command, req); + return NT_STATUS_OK; +} + +/**************************************************************************** +return a string containing the function name of a SMB command +****************************************************************************/ +static const char *smb_fn_name(uint8_t type) +{ + const char *unknown_name = "SMBunknown"; + + if (smb_messages[type].name == NULL) + return unknown_name; + + return smb_messages[type].name; +} + + +/**************************************************************************** + Do a switch on the message type and call the specific reply function for this +message. Unlike earlier versions of Samba the reply functions are responsible +for sending the reply themselves, rather than returning a size to this function +The reply functions may also choose to delay the processing by pushing the message +onto the message queue +****************************************************************************/ +static void switch_message(int type, struct smbsrv_request *req) +{ + int flags; + struct smbsrv_connection *smb_conn = req->smb_conn; + NTSTATUS status; + struct server_id_buf idbuf; + + type &= 0xff; + + errno = 0; + + if (smb_messages[type].fn == NULL) { + DEBUG(0,("Unknown message type %d!\n",type)); + smbsrv_reply_unknown(req); + return; + } + + flags = smb_messages[type].flags; + + req->tcon = smbsrv_smb_tcon_find(smb_conn, SVAL(req->in.hdr,HDR_TID), req->request_time); + + if (!req->session) { + /* setup the user context for this request if it + hasn't already been initialised (to cope with SMB + chaining) */ + + req->session = smbsrv_session_find(req->smb_conn, SVAL(req->in.hdr,HDR_UID), req->request_time); + } + + DEBUG(5, ("switch message %s (task_id %s)\n", + smb_fn_name(type), + server_id_str_buf(req->smb_conn->connection->server_id, + &idbuf))); + + /* this must be called before we do any reply */ + if (flags & SIGNING_NO_REPLY) { + smbsrv_signing_no_reply(req); + } + + /* see if the vuid is valid */ + if ((flags & NEED_SESS) && !req->session) { + status = NT_STATUS_DOS(ERRSRV, ERRbaduid); + /* amazingly, the error code depends on the command */ + switch (type) { + case SMBntcreateX: + case SMBntcancel: + case SMBulogoffX: + break; + default: + if (req->smb_conn->config.nt_status_support && + req->smb_conn->negotiate.client_caps & CAP_STATUS32) { + status = NT_STATUS_INVALID_HANDLE; + } + break; + } + /* + * TODO: + * don't know how to handle smb signing for this case + * so just skip the reply + */ + if ((flags & SIGNING_NO_REPLY) && + (req->smb_conn->signing.signing_state != SMB_SIGNING_ENGINE_OFF)) { + DEBUG(1,("SKIP ERROR REPLY: %s %s because of unknown smb signing case\n", + smb_fn_name(type), nt_errstr(status))); + talloc_free(req); + return; + } + smbsrv_send_error(req, status); + return; + } + + /* does this protocol need a valid tree connection? */ + if ((flags & NEED_TCON) && !req->tcon) { + status = NT_STATUS_DOS(ERRSRV, ERRinvnid); + /* amazingly, the error code depends on the command */ + switch (type) { + case SMBntcreateX: + case SMBntcancel: + case SMBtdis: + break; + default: + if (req->smb_conn->config.nt_status_support && + req->smb_conn->negotiate.client_caps & CAP_STATUS32) { + status = NT_STATUS_INVALID_HANDLE; + } + break; + } + /* + * TODO: + * don't know how to handle smb signing for this case + * so just skip the reply + */ + if ((flags & SIGNING_NO_REPLY) && + (req->smb_conn->signing.signing_state != SMB_SIGNING_ENGINE_OFF)) { + DEBUG(1,("SKIP ERROR REPLY: %s %s because of unknown smb signing case\n", + smb_fn_name(type), nt_errstr(status))); + talloc_free(req); + return; + } + smbsrv_send_error(req, status); + return; + } + + smb_messages[type].fn(req); +} + +/* + we call this when first first part of a possibly chained request has been completed + and we need to call the 2nd part, if any +*/ +void smbsrv_chain_reply(struct smbsrv_request *req) +{ + uint16_t chain_cmd, chain_offset; + uint8_t *vwv, *data; + uint16_t wct; + uint16_t data_size; + + if (req->in.wct < 2 || req->out.wct < 2) { + smbsrv_send_error(req, NT_STATUS_DOS(ERRSRV, ERRerror)); + return; + } + + chain_cmd = CVAL(req->in.vwv, VWV(0)); + chain_offset = SVAL(req->in.vwv, VWV(1)); + + if (chain_cmd == SMB_CHAIN_NONE) { + /* end of chain */ + SSVAL(req->out.vwv, VWV(0), SMB_CHAIN_NONE); + SSVAL(req->out.vwv, VWV(1), 0); + smbsrv_send_reply(req); + return; + } + + if (chain_offset + req->in.hdr >= req->in.buffer + req->in.size) { + goto error; + } + + wct = CVAL(req->in.hdr, chain_offset); + vwv = req->in.hdr + chain_offset + 1; + + if (vwv + VWV(wct) + 2 > req->in.buffer + req->in.size) { + goto error; + } + + data_size = SVAL(vwv, VWV(wct)); + data = vwv + VWV(wct) + 2; + + if (data + data_size > req->in.buffer + req->in.size) { + goto error; + } + + /* all seems legit */ + req->in.vwv = vwv; + req->in.wct = wct; + req->in.data = data; + req->in.data_size = data_size; + req->in.ptr = data; + + /* fix the bufinfo */ + smbsrv_setup_bufinfo(req); + + req->chain_count++; + + SSVAL(req->out.vwv, VWV(0), chain_cmd); + SSVAL(req->out.vwv, VWV(1), req->out.size - NBT_HDR_SIZE); + + /* cleanup somestuff for the next request */ + DLIST_REMOVE(req->smb_conn->requests, req); + talloc_unlink(req, req->ntvfs); + req->ntvfs = NULL; + talloc_free(req->io_ptr); + req->io_ptr = NULL; + + switch_message(chain_cmd, req); + return; + +error: + SSVAL(req->out.vwv, VWV(0), SMB_CHAIN_NONE); + SSVAL(req->out.vwv, VWV(1), 0); + smbsrv_send_error(req, NT_STATUS_DOS(ERRSRV, ERRerror)); +} + +/* + * init the SMB protocol related stuff + */ +NTSTATUS smbsrv_init_smb_connection(struct smbsrv_connection *smb_conn, struct loadparm_context *lp_ctx) +{ + 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(lp_ctx); + + smb_conn->negotiate.zone_offset = get_time_zone(time(NULL)); + + smb_conn->config.nt_status_support = lpcfg_nt_status_support(lp_ctx); + + status = smbsrv_init_sessions(smb_conn, UINT16_MAX); + NT_STATUS_NOT_OK_RETURN(status); + + status = smbsrv_smb_init_tcons(smb_conn); + NT_STATUS_NOT_OK_RETURN(status); + + smbsrv_init_signing(smb_conn); + + return NT_STATUS_OK; +} diff --git a/source4/smb_server/smb/reply.c b/source4/smb_server/smb/reply.c new file mode 100644 index 0000000..8511b8f --- /dev/null +++ b/source4/smb_server/smb/reply.c @@ -0,0 +1,2379 @@ +/* + Unix SMB/CIFS implementation. + Main SMB reply routines + Copyright (C) Andrew Tridgell 1992-2003 + Copyright (C) James J Myers 2003 <myersjj@samba.org> + 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 most of the reply_ calls that the server + makes to handle specific SMB commands +*/ + +#include "includes.h" +#include "smb_server/smb_server.h" +#include "ntvfs/ntvfs.h" +#include "librpc/gen_ndr/ndr_nbt.h" +#include "libcli/nbt/libnbt.h" + + +/**************************************************************************** + Reply to a simple request (async send) +****************************************************************************/ +static void reply_simple_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + + SMBSRV_CHECK_ASYNC_STATUS_SIMPLE; + + smbsrv_setup_reply(req, 0, 0); + smbsrv_send_reply(req); +} + + +/**************************************************************************** + Reply to a tcon (async reply) +****************************************************************************/ +static void reply_tcon_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_tcon *con; + + SMBSRV_CHECK_ASYNC_STATUS(con, union smb_tcon); + + /* construct reply */ + smbsrv_setup_reply(req, 2, 0); + + SSVAL(req->out.vwv, VWV(0), con->tcon.out.max_xmit); + SSVAL(req->out.vwv, VWV(1), con->tcon.out.tid); + SSVAL(req->out.hdr, HDR_TID, req->tcon->tid); + + smbsrv_send_reply(req); +} + +/**************************************************************************** + Reply to a tcon. +****************************************************************************/ +void smbsrv_reply_tcon(struct smbsrv_request *req) +{ + union smb_tcon *con; + NTSTATUS status; + uint8_t *p; + + /* parse request */ + SMBSRV_CHECK_WCT(req, 0); + + SMBSRV_TALLOC_IO_PTR(con, union smb_tcon); + + con->tcon.level = RAW_TCON_TCON; + + p = req->in.data; + p += req_pull_ascii4(&req->in.bufinfo, &con->tcon.in.service, p, STR_TERMINATE); + p += req_pull_ascii4(&req->in.bufinfo, &con->tcon.in.password, p, STR_TERMINATE); + p += req_pull_ascii4(&req->in.bufinfo, &con->tcon.in.dev, p, STR_TERMINATE); + + if (!con->tcon.in.service || !con->tcon.in.password || !con->tcon.in.dev) { + smbsrv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + + /* Instantiate backend */ + status = smbsrv_tcon_backend(req, con); + if (!NT_STATUS_IS_OK(status)) { + smbsrv_send_error(req, status); + return; + } + + SMBSRV_SETUP_NTVFS_REQUEST(reply_tcon_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + /* Invoke NTVFS connection hook */ + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_connect(req->ntvfs, con)); +} + + +/**************************************************************************** + Reply to a tcon and X (async reply) +****************************************************************************/ +static void reply_tcon_and_X_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_tcon *con; + + SMBSRV_CHECK_ASYNC_STATUS(con, union smb_tcon); + + /* construct reply - two variants */ + if (req->smb_conn->negotiate.protocol < PROTOCOL_NT1) { + smbsrv_setup_reply(req, 2, 0); + + SSVAL(req->out.vwv, VWV(0), SMB_CHAIN_NONE); + SSVAL(req->out.vwv, VWV(1), 0); + + req_push_str(req, NULL, con->tconx.out.dev_type, -1, STR_TERMINATE|STR_ASCII); + } else { + smbsrv_setup_reply(req, 3, 0); + + SSVAL(req->out.vwv, VWV(0), SMB_CHAIN_NONE); + SSVAL(req->out.vwv, VWV(1), 0); + SSVAL(req->out.vwv, VWV(2), con->tconx.out.options); + + req_push_str(req, NULL, con->tconx.out.dev_type, -1, STR_TERMINATE|STR_ASCII); + req_push_str(req, NULL, con->tconx.out.fs_type, -1, STR_TERMINATE); + } + + /* set the incoming and outgoing tid to the just created one */ + SSVAL(req->in.hdr, HDR_TID, con->tconx.out.tid); + SSVAL(req->out.hdr,HDR_TID, con->tconx.out.tid); + + smbsrv_chain_reply(req); +} + +/**************************************************************************** + Reply to a tcon and X. +****************************************************************************/ +void smbsrv_reply_tcon_and_X(struct smbsrv_request *req) +{ + NTSTATUS status; + union smb_tcon *con; + uint8_t *p; + uint16_t passlen; + + SMBSRV_TALLOC_IO_PTR(con, union smb_tcon); + + con->tconx.level = RAW_TCON_TCONX; + + /* parse request */ + SMBSRV_CHECK_WCT(req, 4); + + con->tconx.in.flags = SVAL(req->in.vwv, VWV(2)); + passlen = SVAL(req->in.vwv, VWV(3)); + + p = req->in.data; + + if (!req_pull_blob(&req->in.bufinfo, p, passlen, &con->tconx.in.password)) { + smbsrv_send_error(req, NT_STATUS_ILL_FORMED_PASSWORD); + return; + } + p += passlen; + + p += req_pull_string(&req->in.bufinfo, &con->tconx.in.path, p, -1, STR_TERMINATE); + p += req_pull_string(&req->in.bufinfo, &con->tconx.in.device, p, -1, STR_ASCII); + + if (!con->tconx.in.path || !con->tconx.in.device) { + smbsrv_send_error(req, NT_STATUS_BAD_DEVICE_TYPE); + return; + } + + /* Instantiate backend */ + status = smbsrv_tcon_backend(req, con); + if (!NT_STATUS_IS_OK(status)) { + smbsrv_send_error(req, status); + return; + } + + SMBSRV_SETUP_NTVFS_REQUEST(reply_tcon_and_X_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + /* Invoke NTVFS connection hook */ + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_connect(req->ntvfs, con)); +} + + +/**************************************************************************** + Reply to an unknown request +****************************************************************************/ +void smbsrv_reply_unknown(struct smbsrv_request *req) +{ + int type; + + type = CVAL(req->in.hdr, HDR_COM); + + DEBUG(0,("unknown command type %d (0x%X)\n", type, type)); + + smbsrv_send_error(req, NT_STATUS_DOS(ERRSRV, ERRunknownsmb)); +} + + +/**************************************************************************** + Reply to an ioctl (async reply) +****************************************************************************/ +static void reply_ioctl_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_ioctl *io; + + SMBSRV_CHECK_ASYNC_STATUS(io, union smb_ioctl); + + /* the +1 is for nicer alignment */ + smbsrv_setup_reply(req, 8, io->ioctl.out.blob.length+1); + SSVAL(req->out.vwv, VWV(1), io->ioctl.out.blob.length); + SSVAL(req->out.vwv, VWV(5), io->ioctl.out.blob.length); + SSVAL(req->out.vwv, VWV(6), PTR_DIFF(req->out.data, req->out.hdr) + 1); + + memcpy(req->out.data+1, io->ioctl.out.blob.data, io->ioctl.out.blob.length); + + smbsrv_send_reply(req); +} + +/**************************************************************************** + Reply to an ioctl. +****************************************************************************/ +void smbsrv_reply_ioctl(struct smbsrv_request *req) +{ + union smb_ioctl *io; + + /* parse request */ + SMBSRV_CHECK_WCT(req, 3); + SMBSRV_TALLOC_IO_PTR(io, union smb_ioctl); + SMBSRV_SETUP_NTVFS_REQUEST(reply_ioctl_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + io->ioctl.level = RAW_IOCTL_IOCTL; + io->ioctl.in.file.ntvfs = smbsrv_pull_fnum(req, req->in.vwv, VWV(0)); + io->ioctl.in.request = IVAL(req->in.vwv, VWV(1)); + + SMBSRV_CHECK_FILE_HANDLE_ERROR(io->ioctl.in.file.ntvfs, + NT_STATUS_DOS(ERRSRV, ERRerror)); + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_ioctl(req->ntvfs, io)); +} + + +/**************************************************************************** + Reply to a chkpth. +****************************************************************************/ +void smbsrv_reply_chkpth(struct smbsrv_request *req) +{ + union smb_chkpath *io; + + SMBSRV_TALLOC_IO_PTR(io, union smb_chkpath); + SMBSRV_SETUP_NTVFS_REQUEST(reply_simple_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + req_pull_ascii4(&req->in.bufinfo, &io->chkpath.in.path, req->in.data, STR_TERMINATE); + + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_chkpath(req->ntvfs, io)); +} + +/**************************************************************************** + Reply to a getatr (async reply) +****************************************************************************/ +static void reply_getatr_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_fileinfo *st; + + SMBSRV_CHECK_ASYNC_STATUS(st, union smb_fileinfo); + + /* construct reply */ + smbsrv_setup_reply(req, 10, 0); + + SSVAL(req->out.vwv, VWV(0), st->getattr.out.attrib); + srv_push_dos_date3(req->smb_conn, req->out.vwv, VWV(1), st->getattr.out.write_time); + SIVAL(req->out.vwv, VWV(3), st->getattr.out.size); + + SMBSRV_VWV_RESERVED(5, 5); + + smbsrv_send_reply(req); +} + + +/**************************************************************************** + Reply to a getatr. +****************************************************************************/ +void smbsrv_reply_getatr(struct smbsrv_request *req) +{ + union smb_fileinfo *st; + + SMBSRV_TALLOC_IO_PTR(st, union smb_fileinfo); + SMBSRV_SETUP_NTVFS_REQUEST(reply_getatr_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + st->getattr.level = RAW_FILEINFO_GETATTR; + + /* parse request */ + req_pull_ascii4(&req->in.bufinfo, &st->getattr.in.file.path, req->in.data, STR_TERMINATE); + if (!st->getattr.in.file.path) { + smbsrv_send_error(req, NT_STATUS_OBJECT_NAME_NOT_FOUND); + return; + } + + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_qpathinfo(req->ntvfs, st)); +} + + +/**************************************************************************** + Reply to a setatr. +****************************************************************************/ +void smbsrv_reply_setatr(struct smbsrv_request *req) +{ + union smb_setfileinfo *st; + + /* parse request */ + SMBSRV_CHECK_WCT(req, 8); + SMBSRV_TALLOC_IO_PTR(st, union smb_setfileinfo); + SMBSRV_SETUP_NTVFS_REQUEST(reply_simple_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + st->setattr.level = RAW_SFILEINFO_SETATTR; + st->setattr.in.attrib = SVAL(req->in.vwv, VWV(0)); + st->setattr.in.write_time = srv_pull_dos_date3(req->smb_conn, req->in.vwv + VWV(1)); + + req_pull_ascii4(&req->in.bufinfo, &st->setattr.in.file.path, req->in.data, STR_TERMINATE); + + if (!st->setattr.in.file.path) { + smbsrv_send_error(req, NT_STATUS_OBJECT_NAME_NOT_FOUND); + return; + } + + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_setpathinfo(req->ntvfs, st)); +} + + +/**************************************************************************** + Reply to a dskattr (async reply) +****************************************************************************/ +static void reply_dskattr_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_fsinfo *fs; + + SMBSRV_CHECK_ASYNC_STATUS(fs, union smb_fsinfo); + + /* construct reply */ + smbsrv_setup_reply(req, 5, 0); + + SSVAL(req->out.vwv, VWV(0), fs->dskattr.out.units_total); + SSVAL(req->out.vwv, VWV(1), fs->dskattr.out.blocks_per_unit); + SSVAL(req->out.vwv, VWV(2), fs->dskattr.out.block_size); + SSVAL(req->out.vwv, VWV(3), fs->dskattr.out.units_free); + + SMBSRV_VWV_RESERVED(4, 1); + + smbsrv_send_reply(req); +} + + +/**************************************************************************** + Reply to a dskattr. +****************************************************************************/ +void smbsrv_reply_dskattr(struct smbsrv_request *req) +{ + union smb_fsinfo *fs; + + SMBSRV_TALLOC_IO_PTR(fs, union smb_fsinfo); + SMBSRV_SETUP_NTVFS_REQUEST(reply_dskattr_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + fs->dskattr.level = RAW_QFS_DSKATTR; + + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_fsinfo(req->ntvfs, fs)); +} + + +/**************************************************************************** + Reply to an open (async reply) +****************************************************************************/ +static void reply_open_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_open *oi; + + SMBSRV_CHECK_ASYNC_STATUS(oi, union smb_open); + + /* construct reply */ + smbsrv_setup_reply(req, 7, 0); + + smbsrv_push_fnum(req->out.vwv, VWV(0), oi->openold.out.file.ntvfs); + SSVAL(req->out.vwv, VWV(1), oi->openold.out.attrib); + srv_push_dos_date3(req->smb_conn, req->out.vwv, VWV(2), oi->openold.out.write_time); + SIVAL(req->out.vwv, VWV(4), oi->openold.out.size); + SSVAL(req->out.vwv, VWV(6), oi->openold.out.rmode); + + smbsrv_send_reply(req); +} + +/**************************************************************************** + Reply to an open. +****************************************************************************/ +void smbsrv_reply_open(struct smbsrv_request *req) +{ + union smb_open *oi; + + /* parse request */ + SMBSRV_CHECK_WCT(req, 2); + SMBSRV_TALLOC_IO_PTR(oi, union smb_open); + SMBSRV_SETUP_NTVFS_REQUEST(reply_open_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + oi->openold.level = RAW_OPEN_OPEN; + oi->openold.in.open_mode = SVAL(req->in.vwv, VWV(0)); + oi->openold.in.search_attrs = SVAL(req->in.vwv, VWV(1)); + + req_pull_ascii4(&req->in.bufinfo, &oi->openold.in.fname, req->in.data, STR_TERMINATE); + + if (!oi->openold.in.fname) { + smbsrv_send_error(req, NT_STATUS_OBJECT_NAME_NOT_FOUND); + return; + } + + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_open(req->ntvfs, oi)); +} + + +/**************************************************************************** + Reply to an open and X (async reply) +****************************************************************************/ +static void reply_open_and_X_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_open *oi; + + SMBSRV_CHECK_ASYNC_STATUS(oi, union smb_open); + + /* build the reply */ + if (oi->openx.in.flags & OPENX_FLAGS_EXTENDED_RETURN) { + smbsrv_setup_reply(req, 19, 0); + } else { + smbsrv_setup_reply(req, 15, 0); + } + + SSVAL(req->out.vwv, VWV(0), SMB_CHAIN_NONE); + SSVAL(req->out.vwv, VWV(1), 0); + smbsrv_push_fnum(req->out.vwv, VWV(2), oi->openx.out.file.ntvfs); + SSVAL(req->out.vwv, VWV(3), oi->openx.out.attrib); + srv_push_dos_date3(req->smb_conn, req->out.vwv, VWV(4), oi->openx.out.write_time); + SIVAL(req->out.vwv, VWV(6), oi->openx.out.size); + SSVAL(req->out.vwv, VWV(8), oi->openx.out.access); + SSVAL(req->out.vwv, VWV(9), oi->openx.out.ftype); + SSVAL(req->out.vwv, VWV(10),oi->openx.out.devstate); + SSVAL(req->out.vwv, VWV(11),oi->openx.out.action); + SIVAL(req->out.vwv, VWV(12),oi->openx.out.unique_fid); + SSVAL(req->out.vwv, VWV(14),0); /* reserved */ + if (oi->openx.in.flags & OPENX_FLAGS_EXTENDED_RETURN) { + SIVAL(req->out.vwv, VWV(15),oi->openx.out.access_mask); + SMBSRV_VWV_RESERVED(17, 2); + } + + req->chained_fnum = SVAL(req->out.vwv, VWV(2)); + + smbsrv_chain_reply(req); +} + + +/**************************************************************************** + Reply to an open and X. +****************************************************************************/ +void smbsrv_reply_open_and_X(struct smbsrv_request *req) +{ + union smb_open *oi; + + /* parse the request */ + SMBSRV_CHECK_WCT(req, 15); + SMBSRV_TALLOC_IO_PTR(oi, union smb_open); + SMBSRV_SETUP_NTVFS_REQUEST(reply_open_and_X_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + oi->openx.level = RAW_OPEN_OPENX; + oi->openx.in.flags = SVAL(req->in.vwv, VWV(2)); + oi->openx.in.open_mode = SVAL(req->in.vwv, VWV(3)); + oi->openx.in.search_attrs = SVAL(req->in.vwv, VWV(4)); + oi->openx.in.file_attrs = SVAL(req->in.vwv, VWV(5)); + oi->openx.in.write_time = srv_pull_dos_date3(req->smb_conn, req->in.vwv + VWV(6)); + oi->openx.in.open_func = SVAL(req->in.vwv, VWV(8)); + oi->openx.in.size = IVAL(req->in.vwv, VWV(9)); + oi->openx.in.timeout = IVAL(req->in.vwv, VWV(11)); + + req_pull_ascii4(&req->in.bufinfo, &oi->openx.in.fname, req->in.data, STR_TERMINATE); + + if (!oi->openx.in.fname) { + smbsrv_send_error(req, NT_STATUS_OBJECT_NAME_NOT_FOUND); + return; + } + + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_open(req->ntvfs, oi)); +} + + +/**************************************************************************** + Reply to a mknew or a create. +****************************************************************************/ +static void reply_mknew_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_open *oi; + + SMBSRV_CHECK_ASYNC_STATUS(oi, union smb_open); + + /* build the reply */ + smbsrv_setup_reply(req, 1, 0); + + smbsrv_push_fnum(req->out.vwv, VWV(0), oi->mknew.out.file.ntvfs); + + smbsrv_send_reply(req); +} + + +/**************************************************************************** + Reply to a mknew or a create. +****************************************************************************/ +void smbsrv_reply_mknew(struct smbsrv_request *req) +{ + union smb_open *oi; + + /* parse the request */ + SMBSRV_CHECK_WCT(req, 3); + SMBSRV_TALLOC_IO_PTR(oi, union smb_open); + SMBSRV_SETUP_NTVFS_REQUEST(reply_mknew_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + if (CVAL(req->in.hdr, HDR_COM) == SMBmknew) { + oi->mknew.level = RAW_OPEN_MKNEW; + } else { + oi->mknew.level = RAW_OPEN_CREATE; + } + oi->mknew.in.attrib = SVAL(req->in.vwv, VWV(0)); + oi->mknew.in.write_time = srv_pull_dos_date3(req->smb_conn, req->in.vwv + VWV(1)); + + req_pull_ascii4(&req->in.bufinfo, &oi->mknew.in.fname, req->in.data, STR_TERMINATE); + + if (!oi->mknew.in.fname) { + smbsrv_send_error(req, NT_STATUS_OBJECT_NAME_NOT_FOUND); + return; + } + + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_open(req->ntvfs, oi)); +} + +/**************************************************************************** + Reply to a create temporary file (async reply) +****************************************************************************/ +static void reply_ctemp_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_open *oi; + + SMBSRV_CHECK_ASYNC_STATUS(oi, union smb_open); + + /* build the reply */ + smbsrv_setup_reply(req, 1, 0); + + smbsrv_push_fnum(req->out.vwv, VWV(0), oi->ctemp.out.file.ntvfs); + + /* the returned filename is relative to the directory */ + req_push_str(req, NULL, oi->ctemp.out.name, -1, STR_TERMINATE | STR_ASCII); + + smbsrv_send_reply(req); +} + +/**************************************************************************** + Reply to a create temporary file. +****************************************************************************/ +void smbsrv_reply_ctemp(struct smbsrv_request *req) +{ + union smb_open *oi; + + /* parse the request */ + SMBSRV_CHECK_WCT(req, 3); + SMBSRV_TALLOC_IO_PTR(oi, union smb_open); + SMBSRV_SETUP_NTVFS_REQUEST(reply_ctemp_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + oi->ctemp.level = RAW_OPEN_CTEMP; + oi->ctemp.in.attrib = SVAL(req->in.vwv, VWV(0)); + oi->ctemp.in.write_time = srv_pull_dos_date3(req->smb_conn, req->in.vwv + VWV(1)); + + /* the filename is actually a directory name, the server provides a filename + in that directory */ + req_pull_ascii4(&req->in.bufinfo, &oi->ctemp.in.directory, req->in.data, STR_TERMINATE); + + if (!oi->ctemp.in.directory) { + smbsrv_send_error(req, NT_STATUS_OBJECT_NAME_NOT_FOUND); + return; + } + + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_open(req->ntvfs, oi)); +} + + +/**************************************************************************** + Reply to a unlink +****************************************************************************/ +void smbsrv_reply_unlink(struct smbsrv_request *req) +{ + union smb_unlink *unl; + + /* parse the request */ + SMBSRV_CHECK_WCT(req, 1); + SMBSRV_TALLOC_IO_PTR(unl, union smb_unlink); + SMBSRV_SETUP_NTVFS_REQUEST(reply_simple_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + unl->unlink.in.attrib = SVAL(req->in.vwv, VWV(0)); + + req_pull_ascii4(&req->in.bufinfo, &unl->unlink.in.pattern, req->in.data, STR_TERMINATE); + + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_unlink(req->ntvfs, unl)); +} + + +/**************************************************************************** + Reply to a readbraw (core+ protocol). + this is a strange packet because it doesn't use a standard SMB header in the reply, + only the 4 byte NBT header + This command must be replied to synchronously +****************************************************************************/ +void smbsrv_reply_readbraw(struct smbsrv_request *req) +{ + NTSTATUS status; + union smb_read io; + + io.readbraw.level = RAW_READ_READBRAW; + + /* there are two variants, one with 10 and one with 8 command words */ + if (req->in.wct < 8) { + goto failed; + } + + io.readbraw.in.file.ntvfs = smbsrv_pull_fnum(req, req->in.vwv, VWV(0)); + io.readbraw.in.offset = IVAL(req->in.vwv, VWV(1)); + io.readbraw.in.maxcnt = SVAL(req->in.vwv, VWV(3)); + io.readbraw.in.mincnt = SVAL(req->in.vwv, VWV(4)); + io.readbraw.in.timeout = IVAL(req->in.vwv, VWV(5)); + + if (!io.readbraw.in.file.ntvfs) { + goto failed; + } + + /* the 64 bit variant */ + if (req->in.wct == 10) { + uint32_t offset_high = IVAL(req->in.vwv, VWV(8)); + io.readbraw.in.offset |= (((off_t)offset_high) << 32); + } + + /* before calling the backend we setup the raw buffer. This + * saves a copy later */ + req->out.size = io.readbraw.in.maxcnt + NBT_HDR_SIZE; + req->out.buffer = talloc_size(req, req->out.size); + if (req->out.buffer == NULL) { + goto failed; + } + SIVAL(req->out.buffer, 0, 0); /* init NBT header */ + + /* tell the backend where to put the data */ + io.readbraw.out.data = req->out.buffer + NBT_HDR_SIZE; + + /* prepare the ntvfs request */ + req->ntvfs = ntvfs_request_create(req->tcon->ntvfs, req, + req->session->session_info, + SVAL(req->in.hdr,HDR_PID), + req->request_time, + req, NULL, 0); + if (!req->ntvfs) { + goto failed; + } + + /* call the backend */ + status = ntvfs_read(req->ntvfs, &io); + if (!NT_STATUS_IS_OK(status)) { + goto failed; + } + + req->out.size = io.readbraw.out.nread + NBT_HDR_SIZE; + + smbsrv_send_reply_nosign(req); + return; + +failed: + /* any failure in readbraw is equivalent to reading zero bytes */ + req->out.size = 4; + req->out.buffer = talloc_size(req, req->out.size); + SIVAL(req->out.buffer, 0, 0); /* init NBT header */ + + smbsrv_send_reply_nosign(req); +} + + +/**************************************************************************** + Reply to a lockread (async reply) +****************************************************************************/ +static void reply_lockread_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_read *io; + + SMBSRV_CHECK_ASYNC_STATUS(io, union smb_read); + + /* trim packet */ + io->lockread.out.nread = MIN(io->lockread.out.nread, + req_max_data(req) - 3); + req_grow_data(req, 3 + io->lockread.out.nread); + + /* construct reply */ + SSVAL(req->out.vwv, VWV(0), io->lockread.out.nread); + SMBSRV_VWV_RESERVED(1, 4); + + SCVAL(req->out.data, 0, SMB_DATA_BLOCK); + SSVAL(req->out.data, 1, io->lockread.out.nread); + + smbsrv_send_reply(req); +} + + +/**************************************************************************** + Reply to a lockread (core+ protocol). + note that the lock is a write lock, not a read lock! +****************************************************************************/ +void smbsrv_reply_lockread(struct smbsrv_request *req) +{ + union smb_read *io; + + /* parse request */ + SMBSRV_CHECK_WCT(req, 5); + SMBSRV_TALLOC_IO_PTR(io, union smb_read); + SMBSRV_SETUP_NTVFS_REQUEST(reply_lockread_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + io->lockread.level = RAW_READ_LOCKREAD; + io->lockread.in.file.ntvfs= smbsrv_pull_fnum(req, req->in.vwv, VWV(0)); + io->lockread.in.count = SVAL(req->in.vwv, VWV(1)); + io->lockread.in.offset = IVAL(req->in.vwv, VWV(2)); + io->lockread.in.remaining = SVAL(req->in.vwv, VWV(4)); + + /* setup the reply packet assuming the maximum possible read */ + smbsrv_setup_reply(req, 5, 3 + io->lockread.in.count); + + /* tell the backend where to put the data */ + io->lockread.out.data = req->out.data + 3; + + SMBSRV_CHECK_FILE_HANDLE(io->lockread.in.file.ntvfs); + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_read(req->ntvfs, io)); +} + + + +/**************************************************************************** + Reply to a read (async reply) +****************************************************************************/ +static void reply_read_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_read *io; + + SMBSRV_CHECK_ASYNC_STATUS(io, union smb_read); + + /* trim packet */ + io->read.out.nread = MIN(io->read.out.nread, + req_max_data(req) - 3); + req_grow_data(req, 3 + io->read.out.nread); + + /* construct reply */ + SSVAL(req->out.vwv, VWV(0), io->read.out.nread); + SMBSRV_VWV_RESERVED(1, 4); + + SCVAL(req->out.data, 0, SMB_DATA_BLOCK); + SSVAL(req->out.data, 1, io->read.out.nread); + + smbsrv_send_reply(req); +} + +/**************************************************************************** + Reply to a read. +****************************************************************************/ +void smbsrv_reply_read(struct smbsrv_request *req) +{ + union smb_read *io; + + /* parse request */ + SMBSRV_CHECK_WCT(req, 5); + SMBSRV_TALLOC_IO_PTR(io, union smb_read); + SMBSRV_SETUP_NTVFS_REQUEST(reply_read_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + io->read.level = RAW_READ_READ; + io->read.in.file.ntvfs = smbsrv_pull_fnum(req, req->in.vwv, VWV(0)); + io->read.in.count = SVAL(req->in.vwv, VWV(1)); + io->read.in.offset = IVAL(req->in.vwv, VWV(2)); + io->read.in.remaining = SVAL(req->in.vwv, VWV(4)); + + /* setup the reply packet assuming the maximum possible read */ + smbsrv_setup_reply(req, 5, 3 + io->read.in.count); + + /* tell the backend where to put the data */ + io->read.out.data = req->out.data + 3; + + SMBSRV_CHECK_FILE_HANDLE(io->read.in.file.ntvfs); + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_read(req->ntvfs, io)); +} + +/**************************************************************************** + Reply to a read and X (async reply) +****************************************************************************/ +static void reply_read_and_X_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_read *io; + + SMBSRV_CHECK_ASYNC_STATUS_ERR(io, union smb_read); + + /* readx reply packets can be over-sized */ + req->control_flags |= SMBSRV_REQ_CONTROL_LARGE; + if (io->readx.in.maxcnt != 0xFFFF && + io->readx.in.mincnt != 0xFFFF) { + req_grow_data(req, 1 + io->readx.out.nread); + SCVAL(req->out.data, 0, 0); /* padding */ + } else { + req_grow_data(req, io->readx.out.nread); + } + + /* construct reply */ + SSVAL(req->out.vwv, VWV(0), SMB_CHAIN_NONE); + SSVAL(req->out.vwv, VWV(1), 0); + SSVAL(req->out.vwv, VWV(2), io->readx.out.remaining); + SSVAL(req->out.vwv, VWV(3), io->readx.out.compaction_mode); + SMBSRV_VWV_RESERVED(4, 1); + SSVAL(req->out.vwv, VWV(5), io->readx.out.nread); + SSVAL(req->out.vwv, VWV(6), PTR_DIFF(io->readx.out.data, req->out.hdr)); + SSVAL(req->out.vwv, VWV(7), (io->readx.out.nread>>16)); + SMBSRV_VWV_RESERVED(8, 4); + + if (!NT_STATUS_IS_OK(req->ntvfs->async_states->status)) { + smbsrv_setup_error(req, req->ntvfs->async_states->status); + } + + smbsrv_chain_reply(req); +} + +/**************************************************************************** + Reply to a read and X. +****************************************************************************/ +void smbsrv_reply_read_and_X(struct smbsrv_request *req) +{ + union smb_read *io; + uint16_t high_part = 0; + + /* parse request */ + if (req->in.wct != 12) { + SMBSRV_CHECK_WCT(req, 10); + } + + SMBSRV_TALLOC_IO_PTR(io, union smb_read); + SMBSRV_SETUP_NTVFS_REQUEST(reply_read_and_X_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + io->readx.level = RAW_READ_READX; + io->readx.in.file.ntvfs = smbsrv_pull_fnum(req, req->in.vwv, VWV(2)); + io->readx.in.offset = IVAL(req->in.vwv, VWV(3)); + io->readx.in.maxcnt = SVAL(req->in.vwv, VWV(5)); + io->readx.in.mincnt = SVAL(req->in.vwv, VWV(6)); + io->readx.in.remaining = SVAL(req->in.vwv, VWV(9)); + if (req->flags2 & FLAGS2_READ_PERMIT_EXECUTE) { + io->readx.in.read_for_execute = true; + } else { + io->readx.in.read_for_execute = false; + } + + if (req->smb_conn->negotiate.protocol == PROTOCOL_NT1) { + high_part = SVAL(req->in.vwv, VWV(7)); + } + if (high_part != UINT16_MAX) { + io->readx.in.maxcnt |= high_part << 16; + } + + /* + * Windows truncates the length to 0x10000 + */ + io->readx.in.maxcnt = MIN(io->readx.in.maxcnt, 0x10000); + + /* the 64 bit variant */ + if (req->in.wct == 12) { + uint32_t offset_high = IVAL(req->in.vwv, VWV(10)); + io->readx.in.offset |= (((uint64_t)offset_high) << 32); + } + + /* setup the reply packet assuming the maximum possible read */ + smbsrv_setup_reply(req, 12, 1 + io->readx.in.maxcnt); + + /* tell the backend where to put the data. Notice the pad byte. */ + if (io->readx.in.maxcnt != 0xFFFF && + io->readx.in.mincnt != 0xFFFF) { + io->readx.out.data = req->out.data + 1; + } else { + io->readx.out.data = req->out.data; + } + + SMBSRV_CHECK_FILE_HANDLE(io->readx.in.file.ntvfs); + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_read(req->ntvfs, io)); +} + + +/**************************************************************************** + Reply to a writebraw (core+ or LANMAN1.0 protocol). +****************************************************************************/ +void smbsrv_reply_writebraw(struct smbsrv_request *req) +{ + smbsrv_send_error(req, NT_STATUS_DOS(ERRSRV, ERRuseSTD)); +} + + +/**************************************************************************** + Reply to a writeunlock (async reply) +****************************************************************************/ +static void reply_writeunlock_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_write *io; + + SMBSRV_CHECK_ASYNC_STATUS(io, union smb_write); + + /* construct reply */ + smbsrv_setup_reply(req, 1, 0); + + SSVAL(req->out.vwv, VWV(0), io->writeunlock.out.nwritten); + + smbsrv_send_reply(req); +} + +/**************************************************************************** + Reply to a writeunlock (core+). +****************************************************************************/ +void smbsrv_reply_writeunlock(struct smbsrv_request *req) +{ + union smb_write *io; + + SMBSRV_CHECK_WCT(req, 5); + SMBSRV_TALLOC_IO_PTR(io, union smb_write); + SMBSRV_SETUP_NTVFS_REQUEST(reply_writeunlock_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + io->writeunlock.level = RAW_WRITE_WRITEUNLOCK; + io->writeunlock.in.file.ntvfs = smbsrv_pull_fnum(req, req->in.vwv, VWV(0)); + io->writeunlock.in.count = SVAL(req->in.vwv, VWV(1)); + io->writeunlock.in.offset = IVAL(req->in.vwv, VWV(2)); + io->writeunlock.in.remaining = SVAL(req->in.vwv, VWV(4)); + io->writeunlock.in.data = req->in.data + 3; + + /* make sure they gave us the data they promised */ + if (io->writeunlock.in.count+3 > req->in.data_size) { + smbsrv_send_error(req, NT_STATUS_FOOBAR); + return; + } + + /* make sure the data block is big enough */ + if (SVAL(req->in.data, 1) < io->writeunlock.in.count) { + smbsrv_send_error(req, NT_STATUS_FOOBAR); + return; + } + + SMBSRV_CHECK_FILE_HANDLE(io->writeunlock.in.file.ntvfs); + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_write(req->ntvfs, io)); +} + + + +/**************************************************************************** + Reply to a write (async reply) +****************************************************************************/ +static void reply_write_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_write *io; + + SMBSRV_CHECK_ASYNC_STATUS(io, union smb_write); + + /* construct reply */ + smbsrv_setup_reply(req, 1, 0); + + SSVAL(req->out.vwv, VWV(0), io->write.out.nwritten); + + smbsrv_send_reply(req); +} + +/**************************************************************************** + Reply to a write +****************************************************************************/ +void smbsrv_reply_write(struct smbsrv_request *req) +{ + union smb_write *io; + + SMBSRV_CHECK_WCT(req, 5); + SMBSRV_TALLOC_IO_PTR(io, union smb_write); + SMBSRV_SETUP_NTVFS_REQUEST(reply_write_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + io->write.level = RAW_WRITE_WRITE; + io->write.in.file.ntvfs = smbsrv_pull_fnum(req, req->in.vwv, VWV(0)); + io->write.in.count = SVAL(req->in.vwv, VWV(1)); + io->write.in.offset = IVAL(req->in.vwv, VWV(2)); + io->write.in.remaining = SVAL(req->in.vwv, VWV(4)); + io->write.in.data = req->in.data + 3; + + /* make sure they gave us the data they promised */ + if (req_data_oob(&req->in.bufinfo, io->write.in.data, io->write.in.count)) { + smbsrv_send_error(req, NT_STATUS_FOOBAR); + return; + } + + /* make sure the data block is big enough */ + if (SVAL(req->in.data, 1) < io->write.in.count) { + smbsrv_send_error(req, NT_STATUS_FOOBAR); + return; + } + + SMBSRV_CHECK_FILE_HANDLE(io->write.in.file.ntvfs); + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_write(req->ntvfs, io)); +} + + +/**************************************************************************** + Reply to a write and X (async reply) +****************************************************************************/ +static void reply_write_and_X_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_write *io; + + SMBSRV_CHECK_ASYNC_STATUS(io, union smb_write); + + /* construct reply */ + smbsrv_setup_reply(req, 6, 0); + + SSVAL(req->out.vwv, VWV(0), SMB_CHAIN_NONE); + SSVAL(req->out.vwv, VWV(1), 0); + SSVAL(req->out.vwv, VWV(2), io->writex.out.nwritten & 0xFFFF); + SSVAL(req->out.vwv, VWV(3), io->writex.out.remaining); + SSVAL(req->out.vwv, VWV(4), io->writex.out.nwritten >> 16); + SMBSRV_VWV_RESERVED(5, 1); + + smbsrv_chain_reply(req); +} + +/**************************************************************************** + Reply to a write and X. +****************************************************************************/ +void smbsrv_reply_write_and_X(struct smbsrv_request *req) +{ + union smb_write *io; + + if (req->in.wct != 14) { + SMBSRV_CHECK_WCT(req, 12); + } + + SMBSRV_TALLOC_IO_PTR(io, union smb_write); + SMBSRV_SETUP_NTVFS_REQUEST(reply_write_and_X_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + io->writex.level = RAW_WRITE_WRITEX; + io->writex.in.file.ntvfs= smbsrv_pull_fnum(req, req->in.vwv, VWV(2)); + io->writex.in.offset = IVAL(req->in.vwv, VWV(3)); + io->writex.in.wmode = SVAL(req->in.vwv, VWV(7)); + io->writex.in.remaining = SVAL(req->in.vwv, VWV(8)); + io->writex.in.count = SVAL(req->in.vwv, VWV(10)); + io->writex.in.data = req->in.hdr + SVAL(req->in.vwv, VWV(11)); + + if (req->in.wct == 14) { + uint32_t offset_high = IVAL(req->in.vwv, VWV(12)); + uint16_t count_high = SVAL(req->in.vwv, VWV(9)); + io->writex.in.offset |= (((uint64_t)offset_high) << 32); + io->writex.in.count |= ((uint32_t)count_high) << 16; + } + + /* make sure the data is in bounds */ + if (req_data_oob(&req->in.bufinfo, io->writex.in.data, io->writex.in.count)) { + smbsrv_send_error(req, NT_STATUS_DOS(ERRSRV, ERRerror)); + return; + } + + SMBSRV_CHECK_FILE_HANDLE(io->writex.in.file.ntvfs); + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_write(req->ntvfs, io)); +} + + +/**************************************************************************** + Reply to a lseek (async reply) +****************************************************************************/ +static void reply_lseek_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_seek *io; + + SMBSRV_CHECK_ASYNC_STATUS(io, union smb_seek); + + /* construct reply */ + smbsrv_setup_reply(req, 2, 0); + + SIVALS(req->out.vwv, VWV(0), io->lseek.out.offset); + + smbsrv_send_reply(req); +} + +/**************************************************************************** + Reply to a lseek. +****************************************************************************/ +void smbsrv_reply_lseek(struct smbsrv_request *req) +{ + union smb_seek *io; + + SMBSRV_CHECK_WCT(req, 4); + SMBSRV_TALLOC_IO_PTR(io, union smb_seek); + SMBSRV_SETUP_NTVFS_REQUEST(reply_lseek_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + io->lseek.in.file.ntvfs = smbsrv_pull_fnum(req, req->in.vwv, VWV(0)); + io->lseek.in.mode = SVAL(req->in.vwv, VWV(1)); + io->lseek.in.offset = IVALS(req->in.vwv, VWV(2)); + + SMBSRV_CHECK_FILE_HANDLE(io->lseek.in.file.ntvfs); + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_seek(req->ntvfs, io)); +} + +/**************************************************************************** + Reply to a flush. +****************************************************************************/ +void smbsrv_reply_flush(struct smbsrv_request *req) +{ + union smb_flush *io; + uint16_t fnum; + + /* parse request */ + SMBSRV_CHECK_WCT(req, 1); + SMBSRV_TALLOC_IO_PTR(io, union smb_flush); + SMBSRV_SETUP_NTVFS_REQUEST(reply_simple_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + fnum = SVAL(req->in.vwv, VWV(0)); + if (fnum == 0xFFFF) { + io->flush_all.level = RAW_FLUSH_ALL; + } else { + io->flush.level = RAW_FLUSH_FLUSH; + io->flush.in.file.ntvfs = smbsrv_pull_fnum(req, req->in.vwv, VWV(0)); + SMBSRV_CHECK_FILE_HANDLE(io->flush.in.file.ntvfs); + } + + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_flush(req->ntvfs, io)); +} + +/**************************************************************************** + Reply to a close + + Note that this has to deal with closing a directory opened by NT SMB's. +****************************************************************************/ +void smbsrv_reply_close(struct smbsrv_request *req) +{ + union smb_close *io; + + /* parse request */ + SMBSRV_CHECK_WCT(req, 3); + SMBSRV_TALLOC_IO_PTR(io, union smb_close); + SMBSRV_SETUP_NTVFS_REQUEST(reply_simple_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + io->close.level = RAW_CLOSE_CLOSE; + io->close.in.file.ntvfs = smbsrv_pull_fnum(req, req->in.vwv, VWV(0)); + io->close.in.write_time = srv_pull_dos_date3(req->smb_conn, req->in.vwv + VWV(1)); + + SMBSRV_CHECK_FILE_HANDLE(io->close.in.file.ntvfs); + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_close(req->ntvfs, io)); +} + + +/**************************************************************************** + Reply to a writeclose (async reply) +****************************************************************************/ +static void reply_writeclose_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_write *io; + + SMBSRV_CHECK_ASYNC_STATUS(io, union smb_write); + + /* construct reply */ + smbsrv_setup_reply(req, 1, 0); + + SSVAL(req->out.vwv, VWV(0), io->write.out.nwritten); + + smbsrv_send_reply(req); +} + +/**************************************************************************** + Reply to a writeclose (Core+ protocol). +****************************************************************************/ +void smbsrv_reply_writeclose(struct smbsrv_request *req) +{ + union smb_write *io; + + /* this one is pretty weird - the wct can be 6 or 12 */ + if (req->in.wct != 12) { + SMBSRV_CHECK_WCT(req, 6); + } + + SMBSRV_TALLOC_IO_PTR(io, union smb_write); + SMBSRV_SETUP_NTVFS_REQUEST(reply_writeclose_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + io->writeclose.level = RAW_WRITE_WRITECLOSE; + io->writeclose.in.file.ntvfs = smbsrv_pull_fnum(req, req->in.vwv, VWV(0)); + io->writeclose.in.count = SVAL(req->in.vwv, VWV(1)); + io->writeclose.in.offset = IVAL(req->in.vwv, VWV(2)); + io->writeclose.in.mtime = srv_pull_dos_date3(req->smb_conn, req->in.vwv + VWV(4)); + io->writeclose.in.data = req->in.data + 1; + + /* make sure they gave us the data they promised */ + if (req_data_oob(&req->in.bufinfo, io->writeclose.in.data, io->writeclose.in.count)) { + smbsrv_send_error(req, NT_STATUS_FOOBAR); + return; + } + + SMBSRV_CHECK_FILE_HANDLE(io->writeclose.in.file.ntvfs); + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_write(req->ntvfs, io)); +} + +/**************************************************************************** + Reply to a lock. +****************************************************************************/ +void smbsrv_reply_lock(struct smbsrv_request *req) +{ + union smb_lock *lck; + + /* parse request */ + SMBSRV_CHECK_WCT(req, 5); + SMBSRV_TALLOC_IO_PTR(lck, union smb_lock); + SMBSRV_SETUP_NTVFS_REQUEST(reply_simple_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + lck->lock.level = RAW_LOCK_LOCK; + lck->lock.in.file.ntvfs = smbsrv_pull_fnum(req, req->in.vwv, VWV(0)); + lck->lock.in.count = IVAL(req->in.vwv, VWV(1)); + lck->lock.in.offset = IVAL(req->in.vwv, VWV(3)); + + SMBSRV_CHECK_FILE_HANDLE(lck->lock.in.file.ntvfs); + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_lock(req->ntvfs, lck)); +} + + +/**************************************************************************** + Reply to a unlock. +****************************************************************************/ +void smbsrv_reply_unlock(struct smbsrv_request *req) +{ + union smb_lock *lck; + + /* parse request */ + SMBSRV_CHECK_WCT(req, 5); + SMBSRV_TALLOC_IO_PTR(lck, union smb_lock); + SMBSRV_SETUP_NTVFS_REQUEST(reply_simple_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + lck->unlock.level = RAW_LOCK_UNLOCK; + lck->unlock.in.file.ntvfs = smbsrv_pull_fnum(req, req->in.vwv, VWV(0)); + lck->unlock.in.count = IVAL(req->in.vwv, VWV(1)); + lck->unlock.in.offset = IVAL(req->in.vwv, VWV(3)); + + SMBSRV_CHECK_FILE_HANDLE(lck->unlock.in.file.ntvfs); + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_lock(req->ntvfs, lck)); +} + + +/**************************************************************************** + Reply to a tdis. +****************************************************************************/ +void smbsrv_reply_tdis(struct smbsrv_request *req) +{ + struct smbsrv_handle *h, *nh; + + SMBSRV_CHECK_WCT(req, 0); + + /* + * TODO: cancel all pending requests on this tcon + */ + + /* + * close all handles on this tcon + */ + for (h=req->tcon->handles.list; h; h=nh) { + nh = h->next; + talloc_free(h); + } + + /* finally destroy the tcon */ + talloc_free(req->tcon); + req->tcon = NULL; + + smbsrv_setup_reply(req, 0, 0); + smbsrv_send_reply(req); +} + + +/**************************************************************************** + Reply to a echo. This is one of the few calls that is handled directly (the + backends don't see it at all) +****************************************************************************/ +void smbsrv_reply_echo(struct smbsrv_request *req) +{ + uint16_t count; + int i; + + SMBSRV_CHECK_WCT(req, 1); + + count = SVAL(req->in.vwv, VWV(0)); + + smbsrv_setup_reply(req, 1, req->in.data_size); + + memcpy(req->out.data, req->in.data, req->in.data_size); + + for (i=1; i <= count;i++) { + struct smbsrv_request *this_req; + + if (i != count) { + this_req = smbsrv_setup_secondary_request(req); + } else { + this_req = req; + } + + SSVAL(this_req->out.vwv, VWV(0), i); + smbsrv_send_reply(this_req); + } +} + + + +/**************************************************************************** + Reply to a printopen (async reply) +****************************************************************************/ +static void reply_printopen_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_open *oi; + + SMBSRV_CHECK_ASYNC_STATUS(oi, union smb_open); + + /* construct reply */ + smbsrv_setup_reply(req, 1, 0); + + smbsrv_push_fnum(req->out.vwv, VWV(0), oi->openold.out.file.ntvfs); + + smbsrv_send_reply(req); +} + +/**************************************************************************** + Reply to a printopen. +****************************************************************************/ +void smbsrv_reply_printopen(struct smbsrv_request *req) +{ + union smb_open *oi; + + /* parse request */ + SMBSRV_CHECK_WCT(req, 2); + SMBSRV_TALLOC_IO_PTR(oi, union smb_open); + SMBSRV_SETUP_NTVFS_REQUEST(reply_printopen_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + oi->splopen.level = RAW_OPEN_SPLOPEN; + oi->splopen.in.setup_length = SVAL(req->in.vwv, VWV(0)); + oi->splopen.in.mode = SVAL(req->in.vwv, VWV(1)); + + req_pull_ascii4(&req->in.bufinfo, &oi->splopen.in.ident, req->in.data, STR_TERMINATE); + + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_open(req->ntvfs, oi)); +} + +/**************************************************************************** + Reply to a printclose. +****************************************************************************/ +void smbsrv_reply_printclose(struct smbsrv_request *req) +{ + union smb_close *io; + + /* parse request */ + SMBSRV_CHECK_WCT(req, 3); + SMBSRV_TALLOC_IO_PTR(io, union smb_close); + SMBSRV_SETUP_NTVFS_REQUEST(reply_simple_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + io->splclose.level = RAW_CLOSE_SPLCLOSE; + io->splclose.in.file.ntvfs = smbsrv_pull_fnum(req, req->in.vwv, VWV(0)); + + SMBSRV_CHECK_FILE_HANDLE(io->splclose.in.file.ntvfs); + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_close(req->ntvfs, io)); +} + +/**************************************************************************** + Reply to a printqueue. +****************************************************************************/ +static void reply_printqueue_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_lpq *lpq; + int i, maxcount; + const unsigned int el_size = 28; + + SMBSRV_CHECK_ASYNC_STATUS(lpq,union smb_lpq); + + /* construct reply */ + smbsrv_setup_reply(req, 2, 0); + + /* truncate the returned list to fit in the negotiated buffer size */ + maxcount = (req_max_data(req) - 3) / el_size; + if (maxcount < lpq->retq.out.count) { + lpq->retq.out.count = maxcount; + } + + /* setup enough space in the reply */ + req_grow_data(req, 3 + el_size*lpq->retq.out.count); + + /* and fill it in */ + SSVAL(req->out.vwv, VWV(0), lpq->retq.out.count); + SSVAL(req->out.vwv, VWV(1), lpq->retq.out.restart_idx); + + SCVAL(req->out.data, 0, SMB_DATA_BLOCK); + SSVAL(req->out.data, 1, el_size*lpq->retq.out.count); + + req->out.ptr = req->out.data + 3; + + for (i=0;i<lpq->retq.out.count;i++) { + srv_push_dos_date2(req->smb_conn, req->out.ptr, 0 , lpq->retq.out.queue[i].time); + SCVAL(req->out.ptr, 4, lpq->retq.out.queue[i].status); + SSVAL(req->out.ptr, 5, lpq->retq.out.queue[i].job); + SIVAL(req->out.ptr, 7, lpq->retq.out.queue[i].size); + SCVAL(req->out.ptr, 11, 0); /* reserved */ + req_push_str(req, req->out.ptr+12, lpq->retq.out.queue[i].user, 16, STR_ASCII); + req->out.ptr += el_size; + } + + smbsrv_send_reply(req); +} + +/**************************************************************************** + Reply to a printqueue. +****************************************************************************/ +void smbsrv_reply_printqueue(struct smbsrv_request *req) +{ + union smb_lpq *lpq; + + /* parse request */ + SMBSRV_CHECK_WCT(req, 2); + SMBSRV_TALLOC_IO_PTR(lpq, union smb_lpq); + SMBSRV_SETUP_NTVFS_REQUEST(reply_printqueue_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + lpq->retq.level = RAW_LPQ_RETQ; + lpq->retq.in.maxcount = SVAL(req->in.vwv, VWV(0)); + lpq->retq.in.startidx = SVAL(req->in.vwv, VWV(1)); + + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_lpq(req->ntvfs, lpq)); +} + + +/**************************************************************************** + Reply to a printwrite. +****************************************************************************/ +void smbsrv_reply_printwrite(struct smbsrv_request *req) +{ + union smb_write *io; + + /* parse request */ + SMBSRV_CHECK_WCT(req, 1); + SMBSRV_TALLOC_IO_PTR(io, union smb_write); + SMBSRV_SETUP_NTVFS_REQUEST(reply_simple_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + if (req->in.data_size < 3) { + smbsrv_send_error(req, NT_STATUS_FOOBAR); + return; + } + + io->splwrite.level = RAW_WRITE_SPLWRITE; + io->splwrite.in.file.ntvfs = smbsrv_pull_fnum(req, req->in.vwv, VWV(0)); + io->splwrite.in.count = SVAL(req->in.data, 1); + io->splwrite.in.data = req->in.data + 3; + + /* make sure they gave us the data they promised */ + if (req_data_oob(&req->in.bufinfo, io->splwrite.in.data, io->splwrite.in.count)) { + smbsrv_send_error(req, NT_STATUS_FOOBAR); + return; + } + + SMBSRV_CHECK_FILE_HANDLE(io->splwrite.in.file.ntvfs); + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_write(req->ntvfs, io)); +} + + +/**************************************************************************** + Reply to a mkdir. +****************************************************************************/ +void smbsrv_reply_mkdir(struct smbsrv_request *req) +{ + union smb_mkdir *io; + + /* parse the request */ + SMBSRV_CHECK_WCT(req, 0); + SMBSRV_TALLOC_IO_PTR(io, union smb_mkdir); + SMBSRV_SETUP_NTVFS_REQUEST(reply_simple_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + io->generic.level = RAW_MKDIR_MKDIR; + req_pull_ascii4(&req->in.bufinfo, &io->mkdir.in.path, req->in.data, STR_TERMINATE); + + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_mkdir(req->ntvfs, io)); +} + + +/**************************************************************************** + Reply to a rmdir. +****************************************************************************/ +void smbsrv_reply_rmdir(struct smbsrv_request *req) +{ + struct smb_rmdir *io; + + /* parse the request */ + SMBSRV_CHECK_WCT(req, 0); + SMBSRV_TALLOC_IO_PTR(io, struct smb_rmdir); + SMBSRV_SETUP_NTVFS_REQUEST(reply_simple_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + req_pull_ascii4(&req->in.bufinfo, &io->in.path, req->in.data, STR_TERMINATE); + + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_rmdir(req->ntvfs, io)); +} + + +/**************************************************************************** + Reply to a mv. +****************************************************************************/ +void smbsrv_reply_mv(struct smbsrv_request *req) +{ + union smb_rename *io; + uint8_t *p; + + /* parse the request */ + SMBSRV_CHECK_WCT(req, 1); + SMBSRV_TALLOC_IO_PTR(io, union smb_rename); + SMBSRV_SETUP_NTVFS_REQUEST(reply_simple_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + io->generic.level = RAW_RENAME_RENAME; + io->rename.in.attrib = SVAL(req->in.vwv, VWV(0)); + + p = req->in.data; + p += req_pull_ascii4(&req->in.bufinfo, &io->rename.in.pattern1, p, STR_TERMINATE); + p += req_pull_ascii4(&req->in.bufinfo, &io->rename.in.pattern2, p, STR_TERMINATE); + + if (!io->rename.in.pattern1 || !io->rename.in.pattern2) { + smbsrv_send_error(req, NT_STATUS_FOOBAR); + return; + } + + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_rename(req->ntvfs, io)); +} + + +/**************************************************************************** + Reply to an NT rename. +****************************************************************************/ +void smbsrv_reply_ntrename(struct smbsrv_request *req) +{ + union smb_rename *io; + uint8_t *p; + + /* parse the request */ + SMBSRV_CHECK_WCT(req, 4); + SMBSRV_TALLOC_IO_PTR(io, union smb_rename); + SMBSRV_SETUP_NTVFS_REQUEST(reply_simple_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + io->generic.level = RAW_RENAME_NTRENAME; + io->ntrename.in.attrib = SVAL(req->in.vwv, VWV(0)); + io->ntrename.in.flags = SVAL(req->in.vwv, VWV(1)); + io->ntrename.in.cluster_size = IVAL(req->in.vwv, VWV(2)); + + p = req->in.data; + p += req_pull_ascii4(&req->in.bufinfo, &io->ntrename.in.old_name, p, STR_TERMINATE); + p += req_pull_ascii4(&req->in.bufinfo, &io->ntrename.in.new_name, p, STR_TERMINATE); + + if (!io->ntrename.in.old_name || !io->ntrename.in.new_name) { + smbsrv_send_error(req, NT_STATUS_FOOBAR); + return; + } + + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_rename(req->ntvfs, io)); +} + +/**************************************************************************** + Reply to a file copy (async reply) +****************************************************************************/ +static void reply_copy_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + struct smb_copy *cp; + + SMBSRV_CHECK_ASYNC_STATUS(cp, struct smb_copy); + + /* build the reply */ + smbsrv_setup_reply(req, 1, 0); + + SSVAL(req->out.vwv, VWV(0), cp->out.count); + + smbsrv_send_reply(req); +} + +/**************************************************************************** + Reply to a file copy. +****************************************************************************/ +void smbsrv_reply_copy(struct smbsrv_request *req) +{ + struct smb_copy *cp; + uint8_t *p; + + /* parse request */ + SMBSRV_CHECK_WCT(req, 3); + SMBSRV_TALLOC_IO_PTR(cp, struct smb_copy); + SMBSRV_SETUP_NTVFS_REQUEST(reply_copy_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + cp->in.tid2 = SVAL(req->in.vwv, VWV(0)); + cp->in.ofun = SVAL(req->in.vwv, VWV(1)); + cp->in.flags = SVAL(req->in.vwv, VWV(2)); + + p = req->in.data; + p += req_pull_ascii4(&req->in.bufinfo, &cp->in.path1, p, STR_TERMINATE); + p += req_pull_ascii4(&req->in.bufinfo, &cp->in.path2, p, STR_TERMINATE); + + if (!cp->in.path1 || !cp->in.path2) { + smbsrv_send_error(req, NT_STATUS_FOOBAR); + return; + } + + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_copy(req->ntvfs, cp)); +} + +/**************************************************************************** + Reply to a lockingX request (async send) +****************************************************************************/ +static void reply_lockingX_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_lock *lck; + + SMBSRV_CHECK_ASYNC_STATUS(lck, union smb_lock); + + /* if it was an oplock break ack then we only send a reply if + there was an error */ + if (lck->lockx.in.ulock_cnt + lck->lockx.in.lock_cnt == 0) { + talloc_free(req); + return; + } + + /* construct reply */ + smbsrv_setup_reply(req, 2, 0); + + SSVAL(req->out.vwv, VWV(0), SMB_CHAIN_NONE); + SSVAL(req->out.vwv, VWV(1), 0); + + smbsrv_chain_reply(req); +} + + +/**************************************************************************** + Reply to a lockingX request. +****************************************************************************/ +void smbsrv_reply_lockingX(struct smbsrv_request *req) +{ + union smb_lock *lck; + unsigned int total_locks, i; + unsigned int lck_size; + uint8_t *p; + + /* parse request */ + SMBSRV_CHECK_WCT(req, 8); + SMBSRV_TALLOC_IO_PTR(lck, union smb_lock); + SMBSRV_SETUP_NTVFS_REQUEST(reply_lockingX_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + lck->lockx.level = RAW_LOCK_LOCKX; + lck->lockx.in.file.ntvfs= smbsrv_pull_fnum(req, req->in.vwv, VWV(2)); + lck->lockx.in.mode = SVAL(req->in.vwv, VWV(3)); + lck->lockx.in.timeout = IVAL(req->in.vwv, VWV(4)); + lck->lockx.in.ulock_cnt = SVAL(req->in.vwv, VWV(6)); + lck->lockx.in.lock_cnt = SVAL(req->in.vwv, VWV(7)); + + total_locks = lck->lockx.in.ulock_cnt + lck->lockx.in.lock_cnt; + + /* there are two variants, one with 64 bit offsets and counts */ + if (lck->lockx.in.mode & LOCKING_ANDX_LARGE_FILES) { + lck_size = 20; + } else { + lck_size = 10; + } + + /* make sure we got the promised data */ + if (req_data_oob(&req->in.bufinfo, req->in.data, total_locks * lck_size)) { + smbsrv_send_error(req, NT_STATUS_FOOBAR); + return; + } + + /* allocate the locks array */ + if (total_locks) { + lck->lockx.in.locks = talloc_array(req, struct smb_lock_entry, + total_locks); + if (lck->lockx.in.locks == NULL) { + smbsrv_send_error(req, NT_STATUS_NO_MEMORY); + return; + } + } + + p = req->in.data; + + /* construct the locks array */ + for (i=0;i<total_locks;i++) { + uint32_t ofs_high=0, count_high=0; + + lck->lockx.in.locks[i].pid = SVAL(p, 0); + + if (lck->lockx.in.mode & LOCKING_ANDX_LARGE_FILES) { + ofs_high = IVAL(p, 4); + lck->lockx.in.locks[i].offset = IVAL(p, 8); + count_high = IVAL(p, 12); + lck->lockx.in.locks[i].count = IVAL(p, 16); + } else { + lck->lockx.in.locks[i].offset = IVAL(p, 2); + lck->lockx.in.locks[i].count = IVAL(p, 6); + } + if (ofs_high != 0 || count_high != 0) { + lck->lockx.in.locks[i].count |= ((uint64_t)count_high) << 32; + lck->lockx.in.locks[i].offset |= ((uint64_t)ofs_high) << 32; + } + p += lck_size; + } + + SMBSRV_CHECK_FILE_HANDLE(lck->lockx.in.file.ntvfs); + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_lock(req->ntvfs, lck)); +} + +/**************************************************************************** + Reply to a SMBreadbmpx (read block multiplex) request. +****************************************************************************/ +void smbsrv_reply_readbmpx(struct smbsrv_request *req) +{ + /* tell the client to not use a multiplexed read - its too broken to use */ + smbsrv_send_error(req, NT_STATUS_DOS(ERRSRV, ERRuseSTD)); +} + + +/**************************************************************************** + Reply to a SMBsetattrE. +****************************************************************************/ +void smbsrv_reply_setattrE(struct smbsrv_request *req) +{ + union smb_setfileinfo *info; + + /* parse request */ + SMBSRV_CHECK_WCT(req, 7); + SMBSRV_TALLOC_IO_PTR(info, union smb_setfileinfo); + SMBSRV_SETUP_NTVFS_REQUEST(reply_simple_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + info->setattre.level = RAW_SFILEINFO_SETATTRE; + info->setattre.in.file.ntvfs = smbsrv_pull_fnum(req, req->in.vwv, VWV(0)); + info->setattre.in.create_time = srv_pull_dos_date2(req->smb_conn, req->in.vwv + VWV(1)); + info->setattre.in.access_time = srv_pull_dos_date2(req->smb_conn, req->in.vwv + VWV(3)); + info->setattre.in.write_time = srv_pull_dos_date2(req->smb_conn, req->in.vwv + VWV(5)); + + SMBSRV_CHECK_FILE_HANDLE(info->setattre.in.file.ntvfs); + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_setfileinfo(req->ntvfs, info)); +} + + +/**************************************************************************** + Reply to a SMBwritebmpx (write block multiplex primary) request. +****************************************************************************/ +void smbsrv_reply_writebmpx(struct smbsrv_request *req) +{ + smbsrv_send_error(req, NT_STATUS_DOS(ERRSRV, ERRuseSTD)); +} + + +/**************************************************************************** + Reply to a SMBwritebs (write block multiplex secondary) request. +****************************************************************************/ +void smbsrv_reply_writebs(struct smbsrv_request *req) +{ + smbsrv_send_error(req, NT_STATUS_DOS(ERRSRV, ERRuseSTD)); +} + + + +/**************************************************************************** + Reply to a SMBgetattrE (async reply) +****************************************************************************/ +static void reply_getattrE_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_fileinfo *info; + + SMBSRV_CHECK_ASYNC_STATUS(info, union smb_fileinfo); + + /* setup reply */ + smbsrv_setup_reply(req, 11, 0); + + srv_push_dos_date2(req->smb_conn, req->out.vwv, VWV(0), info->getattre.out.create_time); + srv_push_dos_date2(req->smb_conn, req->out.vwv, VWV(2), info->getattre.out.access_time); + srv_push_dos_date2(req->smb_conn, req->out.vwv, VWV(4), info->getattre.out.write_time); + SIVAL(req->out.vwv, VWV(6), info->getattre.out.size); + SIVAL(req->out.vwv, VWV(8), info->getattre.out.alloc_size); + SSVAL(req->out.vwv, VWV(10), info->getattre.out.attrib); + + smbsrv_send_reply(req); +} + +/**************************************************************************** + Reply to a SMBgetattrE. +****************************************************************************/ +void smbsrv_reply_getattrE(struct smbsrv_request *req) +{ + union smb_fileinfo *info; + + /* parse request */ + SMBSRV_CHECK_WCT(req, 1); + SMBSRV_TALLOC_IO_PTR(info, union smb_fileinfo); + SMBSRV_SETUP_NTVFS_REQUEST(reply_getattrE_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + info->getattr.level = RAW_FILEINFO_GETATTRE; + info->getattr.in.file.ntvfs = smbsrv_pull_fnum(req, req->in.vwv, VWV(0)); + + SMBSRV_CHECK_FILE_HANDLE(info->getattr.in.file.ntvfs); + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_qfileinfo(req->ntvfs, info)); +} + +void smbsrv_reply_sesssetup_send(struct smbsrv_request *req, + union smb_sesssetup *io, + NTSTATUS status) +{ + switch (io->old.level) { + case RAW_SESSSETUP_OLD: + if (!NT_STATUS_IS_OK(status)) { + smbsrv_send_error(req, status); + return; + } + + /* construct reply */ + smbsrv_setup_reply(req, 3, 0); + + SSVAL(req->out.vwv, VWV(0), SMB_CHAIN_NONE); + SSVAL(req->out.vwv, VWV(1), 0); + SSVAL(req->out.vwv, VWV(2), io->old.out.action); + + SSVAL(req->out.hdr, HDR_UID, io->old.out.vuid); + + smbsrv_chain_reply(req); + return; + + case RAW_SESSSETUP_NT1: + if (!NT_STATUS_IS_OK(status)) { + smbsrv_send_error(req, status); + return; + } + + /* construct reply */ + smbsrv_setup_reply(req, 3, 0); + + SSVAL(req->out.vwv, VWV(0), SMB_CHAIN_NONE); + SSVAL(req->out.vwv, VWV(1), 0); + SSVAL(req->out.vwv, VWV(2), io->nt1.out.action); + + SSVAL(req->out.hdr, HDR_UID, io->nt1.out.vuid); + + req_push_str(req, NULL, io->nt1.out.os, -1, STR_TERMINATE); + req_push_str(req, NULL, io->nt1.out.lanman, -1, STR_TERMINATE); + req_push_str(req, NULL, io->nt1.out.domain, -1, STR_TERMINATE); + + smbsrv_chain_reply(req); + return; + + case RAW_SESSSETUP_SPNEGO: + if (!NT_STATUS_IS_OK(status) && + !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + smbsrv_send_error(req, status); + return; + } + + /* construct reply */ + smbsrv_setup_reply(req, 4, io->spnego.out.secblob.length); + + if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + smbsrv_setup_error(req, status); + } + + SSVAL(req->out.vwv, VWV(0), SMB_CHAIN_NONE); + SSVAL(req->out.vwv, VWV(1), 0); + SSVAL(req->out.vwv, VWV(2), io->spnego.out.action); + SSVAL(req->out.vwv, VWV(3), io->spnego.out.secblob.length); + + SSVAL(req->out.hdr, HDR_UID, io->spnego.out.vuid); + + memcpy(req->out.data, io->spnego.out.secblob.data, io->spnego.out.secblob.length); + req_push_str(req, NULL, io->spnego.out.os, -1, STR_TERMINATE); + req_push_str(req, NULL, io->spnego.out.lanman, -1, STR_TERMINATE); + req_push_str(req, NULL, io->spnego.out.workgroup, -1, STR_TERMINATE); + + smbsrv_chain_reply(req); + return; + + case RAW_SESSSETUP_SMB2: + break; + } + + smbsrv_send_error(req, NT_STATUS_INTERNAL_ERROR); +} + +/**************************************************************************** +reply to an old style session setup command +****************************************************************************/ +static void reply_sesssetup_old(struct smbsrv_request *req) +{ + uint8_t *p; + uint16_t passlen; + union smb_sesssetup *io; + + SMBSRV_TALLOC_IO_PTR(io, union smb_sesssetup); + + io->old.level = RAW_SESSSETUP_OLD; + + /* parse request */ + io->old.in.bufsize = SVAL(req->in.vwv, VWV(2)); + io->old.in.mpx_max = SVAL(req->in.vwv, VWV(3)); + io->old.in.vc_num = SVAL(req->in.vwv, VWV(4)); + io->old.in.sesskey = IVAL(req->in.vwv, VWV(5)); + passlen = SVAL(req->in.vwv, VWV(7)); + + /* check the request isn't malformed */ + if (req_data_oob(&req->in.bufinfo, req->in.data, passlen)) { + smbsrv_send_error(req, NT_STATUS_FOOBAR); + return; + } + + p = req->in.data; + if (!req_pull_blob(&req->in.bufinfo, p, passlen, &io->old.in.password)) { + smbsrv_send_error(req, NT_STATUS_FOOBAR); + return; + } + p += passlen; + + p += req_pull_string(&req->in.bufinfo, &io->old.in.user, p, -1, STR_TERMINATE); + p += req_pull_string(&req->in.bufinfo, &io->old.in.domain, p, -1, STR_TERMINATE); + p += req_pull_string(&req->in.bufinfo, &io->old.in.os, p, -1, STR_TERMINATE); + p += req_pull_string(&req->in.bufinfo, &io->old.in.lanman, p, -1, STR_TERMINATE); + + /* call the generic handler */ + smbsrv_sesssetup_backend(req, io); +} + +/**************************************************************************** +reply to an NT1 style session setup command +****************************************************************************/ +static void reply_sesssetup_nt1(struct smbsrv_request *req) +{ + uint8_t *p; + uint16_t passlen1, passlen2; + union smb_sesssetup *io; + + SMBSRV_TALLOC_IO_PTR(io, union smb_sesssetup); + + io->nt1.level = RAW_SESSSETUP_NT1; + + /* parse request */ + io->nt1.in.bufsize = SVAL(req->in.vwv, VWV(2)); + io->nt1.in.mpx_max = SVAL(req->in.vwv, VWV(3)); + io->nt1.in.vc_num = SVAL(req->in.vwv, VWV(4)); + io->nt1.in.sesskey = IVAL(req->in.vwv, VWV(5)); + passlen1 = SVAL(req->in.vwv, VWV(7)); + passlen2 = SVAL(req->in.vwv, VWV(8)); + io->nt1.in.capabilities = IVAL(req->in.vwv, VWV(11)); + + /* check the request isn't malformed */ + if (req_data_oob(&req->in.bufinfo, req->in.data, passlen1) || + req_data_oob(&req->in.bufinfo, req->in.data + passlen1, passlen2)) { + smbsrv_send_error(req, NT_STATUS_FOOBAR); + return; + } + + p = req->in.data; + if (!req_pull_blob(&req->in.bufinfo, p, passlen1, &io->nt1.in.password1)) { + smbsrv_send_error(req, NT_STATUS_FOOBAR); + return; + } + p += passlen1; + if (!req_pull_blob(&req->in.bufinfo, p, passlen2, &io->nt1.in.password2)) { + smbsrv_send_error(req, NT_STATUS_FOOBAR); + return; + } + p += passlen2; + + p += req_pull_string(&req->in.bufinfo, &io->nt1.in.user, p, -1, STR_TERMINATE); + p += req_pull_string(&req->in.bufinfo, &io->nt1.in.domain, p, -1, STR_TERMINATE); + p += req_pull_string(&req->in.bufinfo, &io->nt1.in.os, p, -1, STR_TERMINATE); + p += req_pull_string(&req->in.bufinfo, &io->nt1.in.lanman, p, -1, STR_TERMINATE); + + /* call the generic handler */ + smbsrv_sesssetup_backend(req, io); +} + + +/**************************************************************************** +reply to an SPNEGO style session setup command +****************************************************************************/ +static void reply_sesssetup_spnego(struct smbsrv_request *req) +{ + uint8_t *p; + uint16_t blob_len; + union smb_sesssetup *io; + + SMBSRV_TALLOC_IO_PTR(io, union smb_sesssetup); + + io->spnego.level = RAW_SESSSETUP_SPNEGO; + + /* parse request */ + io->spnego.in.bufsize = SVAL(req->in.vwv, VWV(2)); + io->spnego.in.mpx_max = SVAL(req->in.vwv, VWV(3)); + io->spnego.in.vc_num = SVAL(req->in.vwv, VWV(4)); + io->spnego.in.sesskey = IVAL(req->in.vwv, VWV(5)); + blob_len = SVAL(req->in.vwv, VWV(7)); + io->spnego.in.capabilities = IVAL(req->in.vwv, VWV(10)); + + p = req->in.data; + if (!req_pull_blob(&req->in.bufinfo, p, blob_len, &io->spnego.in.secblob)) { + smbsrv_send_error(req, NT_STATUS_FOOBAR); + return; + } + p += blob_len; + + p += req_pull_string(&req->in.bufinfo, &io->spnego.in.os, p, -1, STR_TERMINATE); + p += req_pull_string(&req->in.bufinfo, &io->spnego.in.lanman, p, -1, STR_TERMINATE); + p += req_pull_string(&req->in.bufinfo, &io->spnego.in.workgroup, p, -1, STR_TERMINATE); + + /* call the generic handler */ + smbsrv_sesssetup_backend(req, io); +} + + +/**************************************************************************** +reply to a session setup command +****************************************************************************/ +void smbsrv_reply_sesssetup(struct smbsrv_request *req) +{ + switch (req->in.wct) { + case 10: + /* a pre-NT1 call */ + reply_sesssetup_old(req); + return; + case 13: + /* a NT1 call */ + reply_sesssetup_nt1(req); + return; + case 12: + /* a SPNEGO call */ + reply_sesssetup_spnego(req); + return; + } + + /* unsupported variant */ + smbsrv_send_error(req, NT_STATUS_FOOBAR); +} + +/**************************************************************************** + Reply to a exit. This closes all files open by a smbpid +****************************************************************************/ +void smbsrv_reply_exit(struct smbsrv_request *req) +{ + struct smbsrv_handle_session_item *i, *ni; + struct smbsrv_handle *h; + struct smbsrv_tcon *tcon; + uint16_t smbpid; + + SMBSRV_CHECK_WCT(req, 0); + + smbpid = SVAL(req->in.hdr,HDR_PID); + + /* first destroy all handles, which have the same PID as the request */ + for (i=req->session->handles; i; i=ni) { + ni = i->next; + h = i->handle; + if (h->smbpid != smbpid) continue; + + talloc_free(h); + } + + /* + * then let the ntvfs backends proxy the call if they want to, + * but we didn't check the return value of the backends, + * as for the SMB client the call succeed + */ + for (tcon=req->smb_conn->smb_tcons.list;tcon;tcon=tcon->next) { + req->tcon = tcon; + SMBSRV_SETUP_NTVFS_REQUEST(NULL,0); + ntvfs_exit(req->ntvfs); + talloc_free(req->ntvfs); + req->ntvfs = NULL; + req->tcon = NULL; + } + + smbsrv_setup_reply(req, 0, 0); + smbsrv_send_reply(req); +} + +/**************************************************************************** + Reply to a SMBulogoffX. +****************************************************************************/ +void smbsrv_reply_ulogoffX(struct smbsrv_request *req) +{ + struct smbsrv_handle_session_item *i, *ni; + struct smbsrv_handle *h; + struct smbsrv_tcon *tcon; + + SMBSRV_CHECK_WCT(req, 2); + + /* + * TODO: cancel all pending requests + */ + + + /* destroy all handles */ + for (i=req->session->handles; i; i=ni) { + ni = i->next; + h = i->handle; + talloc_free(h); + } + + /* + * then let the ntvfs backends proxy the call if they want to, + * but we didn't check the return value of the backends, + * as for the SMB client the call succeed + */ + for (tcon=req->smb_conn->smb_tcons.list;tcon;tcon=tcon->next) { + req->tcon = tcon; + SMBSRV_SETUP_NTVFS_REQUEST(NULL,0); + ntvfs_logoff(req->ntvfs); + talloc_free(req->ntvfs); + req->ntvfs = NULL; + req->tcon = NULL; + } + + talloc_free(req->session); + req->session = NULL; /* it is now invalid, don't use on + any chained packets */ + + smbsrv_setup_reply(req, 2, 0); + + SSVAL(req->out.vwv, VWV(0), SMB_CHAIN_NONE); + SSVAL(req->out.vwv, VWV(1), 0); + + smbsrv_chain_reply(req); +} + +/**************************************************************************** + Reply to an SMBfindclose request +****************************************************************************/ +void smbsrv_reply_findclose(struct smbsrv_request *req) +{ + union smb_search_close *io; + + /* parse request */ + SMBSRV_CHECK_WCT(req, 1); + SMBSRV_TALLOC_IO_PTR(io, union smb_search_close); + SMBSRV_SETUP_NTVFS_REQUEST(reply_simple_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + io->findclose.level = RAW_FINDCLOSE_FINDCLOSE; + io->findclose.in.handle = SVAL(req->in.vwv, VWV(0)); + + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_search_close(req->ntvfs, io)); +} + +/**************************************************************************** + Reply to an SMBfindnclose request +****************************************************************************/ +void smbsrv_reply_findnclose(struct smbsrv_request *req) +{ + smbsrv_send_error(req, NT_STATUS_FOOBAR); +} + + +/**************************************************************************** + Reply to an SMBntcreateX request (async send) +****************************************************************************/ +static void reply_ntcreate_and_X_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_open *io; + + SMBSRV_CHECK_ASYNC_STATUS(io, union smb_open); + + /* construct reply */ + smbsrv_setup_reply(req, 34, 0); + + SSVAL(req->out.vwv, VWV(0), SMB_CHAIN_NONE); + SSVAL(req->out.vwv, VWV(1), 0); + SCVAL(req->out.vwv, VWV(2), io->ntcreatex.out.oplock_level); + + /* the rest of the parameters are not aligned! */ + smbsrv_push_fnum(req->out.vwv, 5, io->ntcreatex.out.file.ntvfs); + SIVAL(req->out.vwv, 7, io->ntcreatex.out.create_action); + push_nttime(req->out.vwv, 11, io->ntcreatex.out.create_time); + push_nttime(req->out.vwv, 19, io->ntcreatex.out.access_time); + push_nttime(req->out.vwv, 27, io->ntcreatex.out.write_time); + push_nttime(req->out.vwv, 35, io->ntcreatex.out.change_time); + SIVAL(req->out.vwv, 43, io->ntcreatex.out.attrib); + SBVAL(req->out.vwv, 47, io->ntcreatex.out.alloc_size); + SBVAL(req->out.vwv, 55, io->ntcreatex.out.size); + SSVAL(req->out.vwv, 63, io->ntcreatex.out.file_type); + SSVAL(req->out.vwv, 65, io->ntcreatex.out.ipc_state); + SCVAL(req->out.vwv, 67, io->ntcreatex.out.is_directory); + + req->chained_fnum = SVAL(req->out.vwv, 5); + + smbsrv_chain_reply(req); +} + +/**************************************************************************** + Reply to an SMBntcreateX request +****************************************************************************/ +void smbsrv_reply_ntcreate_and_X(struct smbsrv_request *req) +{ + union smb_open *io; + uint16_t fname_len; + + /* parse the request */ + SMBSRV_CHECK_WCT(req, 24); + SMBSRV_TALLOC_IO_PTR(io, union smb_open); + SMBSRV_SETUP_NTVFS_REQUEST(reply_ntcreate_and_X_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + io->ntcreatex.level = RAW_OPEN_NTCREATEX; + + /* notice that the word parameters are not word aligned, so we don't use VWV() */ + fname_len = SVAL(req->in.vwv, 5); + io->ntcreatex.in.flags = IVAL(req->in.vwv, 7); + io->ntcreatex.in.root_fid.ntvfs = smbsrv_pull_fnum(req, req->in.vwv, 11); + io->ntcreatex.in.access_mask = IVAL(req->in.vwv, 15); + io->ntcreatex.in.alloc_size = BVAL(req->in.vwv, 19); + io->ntcreatex.in.file_attr = IVAL(req->in.vwv, 27); + io->ntcreatex.in.share_access = IVAL(req->in.vwv, 31); + io->ntcreatex.in.open_disposition = IVAL(req->in.vwv, 35); + io->ntcreatex.in.create_options = IVAL(req->in.vwv, 39); + io->ntcreatex.in.impersonation = IVAL(req->in.vwv, 43); + io->ntcreatex.in.security_flags = CVAL(req->in.vwv, 47); + io->ntcreatex.in.ea_list = NULL; + io->ntcreatex.in.sec_desc = NULL; + io->ntcreatex.in.query_maximal_access = false; + io->ntcreatex.in.query_on_disk_id = false; + io->ntcreatex.in.private_flags = 0; + + /* we need a neater way to handle this alignment */ + if ((req->flags2 & FLAGS2_UNICODE_STRINGS) && + ucs2_align(req->in.buffer, req->in.data, STR_TERMINATE|STR_UNICODE)) { + fname_len++; + } + + req_pull_string(&req->in.bufinfo, &io->ntcreatex.in.fname, req->in.data, fname_len, STR_TERMINATE); + if (!io->ntcreatex.in.fname) { + smbsrv_send_error(req, NT_STATUS_FOOBAR); + return; + } + + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_open(req->ntvfs, io)); +} + + +/**************************************************************************** + Reply to an SMBntcancel request +****************************************************************************/ +void smbsrv_reply_ntcancel(struct smbsrv_request *req) +{ + struct smbsrv_request *r; + uint16_t tid = SVAL(req->in.hdr,HDR_TID); + uint16_t uid = SVAL(req->in.hdr,HDR_UID); + uint16_t mid = SVAL(req->in.hdr,HDR_MID); + uint16_t pid = SVAL(req->in.hdr,HDR_PID); + + for (r = req->smb_conn->requests; r; r = r->next) { + if (tid != SVAL(r->in.hdr,HDR_TID)) continue; + if (uid != SVAL(r->in.hdr,HDR_UID)) continue; + if (mid != SVAL(r->in.hdr,HDR_MID)) continue; + if (pid != SVAL(r->in.hdr,HDR_PID)) continue; + + SMBSRV_CHECK(ntvfs_cancel(r->ntvfs)); + + /* NOTE: this request does not generate a reply */ + talloc_free(req); + return; + } + + /* TODO: workout the correct error code, + * until we know how the smb signing works + * for ntcancel replies, don't send an error + */ + /*smbsrv_send_error(req, NT_STATUS_FOOBAR);*/ + talloc_free(req); +} + +/* + parse the called/calling names from session request +*/ +static NTSTATUS parse_session_request(struct smbsrv_request *req) +{ + DATA_BLOB blob; + NTSTATUS status; + + blob.data = req->in.buffer + 4; + blob.length = ascii_len_n((const char *)blob.data, req->in.size - PTR_DIFF(blob.data, req->in.buffer)); + if (blob.length == 0) return NT_STATUS_BAD_NETWORK_NAME; + + req->smb_conn->negotiate.called_name = talloc(req->smb_conn, struct nbt_name); + req->smb_conn->negotiate.calling_name = talloc(req->smb_conn, struct nbt_name); + if (req->smb_conn->negotiate.called_name == NULL || + req->smb_conn->negotiate.calling_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = nbt_name_from_blob(req->smb_conn, &blob, + req->smb_conn->negotiate.called_name); + NT_STATUS_NOT_OK_RETURN(status); + + blob.data += blob.length; + blob.length = ascii_len_n((const char *)blob.data, req->in.size - PTR_DIFF(blob.data, req->in.buffer)); + if (blob.length == 0) return NT_STATUS_BAD_NETWORK_NAME; + + status = nbt_name_from_blob(req->smb_conn, &blob, + req->smb_conn->negotiate.calling_name); + NT_STATUS_NOT_OK_RETURN(status); + + req->smb_conn->negotiate.done_nbt_session = true; + + return NT_STATUS_OK; +} + + + +/**************************************************************************** + Reply to a special message - a SMB packet with non zero NBT message type +****************************************************************************/ +void smbsrv_reply_special(struct smbsrv_request *req) +{ + uint8_t msg_type; + uint8_t *buf = talloc_zero_array(req, uint8_t, 4); + + msg_type = CVAL(req->in.buffer,0); + + SIVAL(buf, 0, 0); + + switch (msg_type) { + case NBSSrequest: /* session request */ + if (req->smb_conn->negotiate.done_nbt_session) { + DEBUG(0,("Warning: ignoring secondary session request\n")); + return; + } + + SCVAL(buf,0,0x82); + SCVAL(buf,3,0); + + /* we don't check the status - samba always accepts session + requests for any name */ + parse_session_request(req); + + req->out.buffer = buf; + req->out.size = 4; + smbsrv_send_reply_nosign(req); + return; + + case 0x89: /* session keepalive request + (some old clients produce this?) */ + SCVAL(buf, 0, NBSSkeepalive); + SCVAL(buf, 3, 0); + req->out.buffer = buf; + req->out.size = 4; + smbsrv_send_reply_nosign(req); + return; + + case NBSSkeepalive: + /* session keepalive - swallow it */ + talloc_free(req); + return; + } + + DEBUG(0,("Unexpected NBT session packet (%d)\n", msg_type)); + talloc_free(req); +} diff --git a/source4/smb_server/smb/request.c b/source4/smb_server/smb/request.c new file mode 100644 index 0000000..0f3171f --- /dev/null +++ b/source4/smb_server/smb/request.c @@ -0,0 +1,779 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Andrew Tridgell 2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* + this file implements functions for manipulating the 'struct smbsrv_request' structure in smbd +*/ + +#include "includes.h" +#include "smb_server/smb_server.h" +#include "samba/service_stream.h" +#include "lib/stream/packet.h" +#include "ntvfs/ntvfs.h" + + +/* we over allocate the data buffer to prevent too many realloc calls */ +#define REQ_OVER_ALLOCATION 0 + +/* setup the bufinfo used for strings and range checking */ +void smbsrv_setup_bufinfo(struct smbsrv_request *req) +{ + req->in.bufinfo.mem_ctx = req; + req->in.bufinfo.flags = 0; + if (req->flags2 & FLAGS2_UNICODE_STRINGS) { + req->in.bufinfo.flags |= BUFINFO_FLAG_UNICODE; + } + req->in.bufinfo.align_base = req->in.buffer; + req->in.bufinfo.data = req->in.data; + req->in.bufinfo.data_size = req->in.data_size; +} + + +static int smbsrv_request_destructor(struct smbsrv_request *req) +{ + DLIST_REMOVE(req->smb_conn->requests, req); + return 0; +} + +/**************************************************************************** +construct a basic request packet, mostly used to construct async packets +such as change notify and oplock break requests +****************************************************************************/ +struct smbsrv_request *smbsrv_init_request(struct smbsrv_connection *smb_conn) +{ + struct smbsrv_request *req; + + req = talloc_zero(smb_conn, struct smbsrv_request); + if (!req) { + return NULL; + } + + /* setup the request context */ + req->smb_conn = smb_conn; + + talloc_set_destructor(req, smbsrv_request_destructor); + + return req; +} + + +/* + setup a chained reply in req->out with the given word count and initial data buffer size. +*/ +static void req_setup_chain_reply(struct smbsrv_request *req, unsigned int wct, unsigned int buflen) +{ + uint32_t chain_base_size = req->out.size; + + /* we need room for the wct value, the words, the buffer length and the buffer */ + req->out.size += 1 + VWV(wct) + 2 + buflen; + + /* over allocate by a small amount */ + req->out.allocated = req->out.size + REQ_OVER_ALLOCATION; + + req->out.buffer = talloc_realloc(req, req->out.buffer, + uint8_t, req->out.allocated); + if (!req->out.buffer) { + smbsrv_terminate_connection(req->smb_conn, "allocation failed"); + return; + } + + req->out.hdr = req->out.buffer + NBT_HDR_SIZE; + req->out.vwv = req->out.buffer + chain_base_size + 1; + req->out.wct = wct; + req->out.data = req->out.vwv + VWV(wct) + 2; + req->out.data_size = buflen; + req->out.ptr = req->out.data; + + SCVAL(req->out.buffer, chain_base_size, wct); + SSVAL(req->out.vwv, VWV(wct), buflen); +} + + +/* + setup a reply in req->out with the given word count and initial data buffer size. + the caller will then fill in the command words and data before calling req_send_reply() to + send the reply on its way +*/ +void smbsrv_setup_reply(struct smbsrv_request *req, unsigned int wct, size_t buflen) +{ + uint16_t flags2; + + if (req->chain_count != 0) { + req_setup_chain_reply(req, wct, buflen); + return; + } + + req->out.size = NBT_HDR_SIZE + MIN_SMB_SIZE + VWV(wct) + buflen; + + /* over allocate by a small amount */ + req->out.allocated = req->out.size + REQ_OVER_ALLOCATION; + + req->out.buffer = talloc_size(req, req->out.allocated); + if (!req->out.buffer) { + smbsrv_terminate_connection(req->smb_conn, "allocation failed"); + return; + } + + flags2 = FLAGS2_LONG_PATH_COMPONENTS | + FLAGS2_EXTENDED_ATTRIBUTES | + FLAGS2_IS_LONG_NAME; +#define _SMB_FLAGS2_ECHOED_FLAGS ( \ + FLAGS2_UNICODE_STRINGS | \ + FLAGS2_EXTENDED_SECURITY | \ + FLAGS2_SMB_SECURITY_SIGNATURES \ +) + flags2 |= (req->flags2 & _SMB_FLAGS2_ECHOED_FLAGS); + if (req->smb_conn->negotiate.client_caps & CAP_STATUS32) { + flags2 |= FLAGS2_32_BIT_ERROR_CODES; + } + + req->out.hdr = req->out.buffer + NBT_HDR_SIZE; + req->out.vwv = req->out.hdr + HDR_VWV; + req->out.wct = wct; + req->out.data = req->out.vwv + VWV(wct) + 2; + req->out.data_size = buflen; + req->out.ptr = req->out.data; + + SIVAL(req->out.hdr, HDR_RCLS, 0); + + SCVAL(req->out.hdr, HDR_WCT, wct); + SSVAL(req->out.vwv, VWV(wct), buflen); + + memcpy(req->out.hdr, "\377SMB", 4); + SCVAL(req->out.hdr,HDR_FLG, FLAG_REPLY | FLAG_CASELESS_PATHNAMES); + SSVAL(req->out.hdr,HDR_FLG2, flags2); + SSVAL(req->out.hdr,HDR_PIDHIGH,0); + memset(req->out.hdr + HDR_SS_FIELD, 0, 10); + + if (req->in.hdr) { + /* copy the cmd, tid, pid, uid and mid from the request */ + SCVAL(req->out.hdr,HDR_COM,CVAL(req->in.hdr,HDR_COM)); + SSVAL(req->out.hdr,HDR_TID,SVAL(req->in.hdr,HDR_TID)); + SSVAL(req->out.hdr,HDR_PID,SVAL(req->in.hdr,HDR_PID)); + SSVAL(req->out.hdr,HDR_UID,SVAL(req->in.hdr,HDR_UID)); + SSVAL(req->out.hdr,HDR_MID,SVAL(req->in.hdr,HDR_MID)); + } else { + SCVAL(req->out.hdr,HDR_COM,0); + SSVAL(req->out.hdr,HDR_TID,0); + SSVAL(req->out.hdr,HDR_PID,0); + SSVAL(req->out.hdr,HDR_UID,0); + SSVAL(req->out.hdr,HDR_MID,0); + } +} + + +/* + setup a copy of a request, used when the server needs to send + more than one reply for a single request packet +*/ +struct smbsrv_request *smbsrv_setup_secondary_request(struct smbsrv_request *old_req) +{ + struct smbsrv_request *req; + ptrdiff_t diff; + + req = talloc_memdup(old_req, old_req, sizeof(struct smbsrv_request)); + if (req == NULL) { + return NULL; + } + + req->out.buffer = talloc_memdup(req, req->out.buffer, req->out.allocated); + if (req->out.buffer == NULL) { + talloc_free(req); + return NULL; + } + + diff = req->out.buffer - old_req->out.buffer; + + req->out.hdr += diff; + req->out.vwv += diff; + req->out.data += diff; + req->out.ptr += diff; + + return req; +} + +/* + work out the maximum data size we will allow for this reply, given + the negotiated max_xmit. The basic reply packet must be setup before + this call + + note that this is deliberately a signed integer reply +*/ +int req_max_data(struct smbsrv_request *req) +{ + int ret; + ret = req->smb_conn->negotiate.max_send; + ret -= PTR_DIFF(req->out.data, req->out.hdr); + if (ret < 0) ret = 0; + return ret; +} + + +/* + grow the allocation of the data buffer portion of a reply + packet. Note that as this can reallocate the packet buffer this + invalidates any local pointers into the packet. + + To cope with this req->out.ptr is supplied. This will be updated to + point at the same offset into the packet as before this call +*/ +static void req_grow_allocation(struct smbsrv_request *req, unsigned int new_size) +{ + int delta; + uint8_t *buf2; + + delta = new_size - req->out.data_size; + if (delta + req->out.size <= req->out.allocated) { + /* it fits in the preallocation */ + return; + } + + /* we need to realloc */ + req->out.allocated = req->out.size + delta + REQ_OVER_ALLOCATION; + buf2 = talloc_realloc(req, req->out.buffer, uint8_t, req->out.allocated); + if (buf2 == NULL) { + smb_panic("out of memory in req_grow_allocation"); + } + + if (buf2 == req->out.buffer) { + /* the malloc library gave us the same pointer */ + return; + } + + /* update the pointers into the packet */ + req->out.data = buf2 + PTR_DIFF(req->out.data, req->out.buffer); + req->out.ptr = buf2 + PTR_DIFF(req->out.ptr, req->out.buffer); + req->out.vwv = buf2 + PTR_DIFF(req->out.vwv, req->out.buffer); + req->out.hdr = buf2 + PTR_DIFF(req->out.hdr, req->out.buffer); + + req->out.buffer = buf2; +} + + +/* + grow the data buffer portion of a reply packet. Note that as this + can reallocate the packet buffer this invalidates any local pointers + into the packet. + + To cope with this req->out.ptr is supplied. This will be updated to + point at the same offset into the packet as before this call +*/ +void req_grow_data(struct smbsrv_request *req, size_t new_size) +{ + int delta; + + if (!(req->control_flags & SMBSRV_REQ_CONTROL_LARGE) && new_size > req_max_data(req)) { + smb_panic("reply buffer too large!"); + } + + req_grow_allocation(req, new_size); + + delta = new_size - req->out.data_size; + + req->out.size += delta; + req->out.data_size += delta; + + /* set the BCC to the new data size */ + SSVAL(req->out.vwv, VWV(req->out.wct), new_size); +} + +/* + send a reply and destroy the request buffer + + note that this only looks at req->out.buffer and req->out.size, allowing manually + constructed packets to be sent +*/ +void smbsrv_send_reply_nosign(struct smbsrv_request *req) +{ + DATA_BLOB blob; + NTSTATUS status; + + if (req->smb_conn->connection->event.fde == NULL) { + /* we are in the process of shutting down this connection */ + talloc_free(req); + return; + } + + if (req->out.size > NBT_HDR_SIZE) { + _smb_setlen_nbt(req->out.buffer, req->out.size - NBT_HDR_SIZE); + } + + 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)); + } + talloc_free(req); +} + +/* + possibly sign a message then send a reply and destroy the request buffer + + note that this only looks at req->out.buffer and req->out.size, allowing manually + constructed packets to be sent +*/ +void smbsrv_send_reply(struct smbsrv_request *req) +{ + if (req->smb_conn->connection->event.fde == NULL) { + /* we are in the process of shutting down this connection */ + talloc_free(req); + return; + } + smbsrv_sign_packet(req); + + smbsrv_send_reply_nosign(req); +} + +/* + setup the header of a reply to include an NTSTATUS code +*/ +void smbsrv_setup_error(struct smbsrv_request *req, NTSTATUS status) +{ + if (!req->smb_conn->config.nt_status_support || !(req->smb_conn->negotiate.client_caps & CAP_STATUS32)) { + /* convert to DOS error codes */ + uint8_t eclass; + uint32_t ecode; + ntstatus_to_dos(status, &eclass, &ecode); + SCVAL(req->out.hdr, HDR_RCLS, eclass); + SSVAL(req->out.hdr, HDR_ERR, ecode); + SSVAL(req->out.hdr, HDR_FLG2, SVAL(req->out.hdr, HDR_FLG2) & ~FLAGS2_32_BIT_ERROR_CODES); + return; + } + + if (NT_STATUS_IS_DOS(status)) { + /* its a encoded DOS error, using the reserved range */ + SSVAL(req->out.hdr, HDR_RCLS, NT_STATUS_DOS_CLASS(status)); + SSVAL(req->out.hdr, HDR_ERR, NT_STATUS_DOS_CODE(status)); + SSVAL(req->out.hdr, HDR_FLG2, SVAL(req->out.hdr, HDR_FLG2) & ~FLAGS2_32_BIT_ERROR_CODES); + } else { + SIVAL(req->out.hdr, HDR_RCLS, NT_STATUS_V(status)); + SSVAL(req->out.hdr, HDR_FLG2, SVAL(req->out.hdr, HDR_FLG2) | FLAGS2_32_BIT_ERROR_CODES); + } +} + +/* + construct and send an error packet, then destroy the request + auto-converts to DOS error format when appropriate +*/ +void smbsrv_send_error(struct smbsrv_request *req, 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; + } + smbsrv_setup_reply(req, 0, 0); + + /* error returns never have any data */ + req_grow_data(req, 0); + + smbsrv_setup_error(req, status); + smbsrv_send_reply(req); +} + + +/* + push a string into the data portion of the request packet, growing it if necessary + this gets quite tricky - please be very careful to cover all cases when modifying this + + if dest is NULL, then put the string at the end of the data portion of the packet + + if dest_len is -1 then no limit applies +*/ +size_t req_push_str(struct smbsrv_request *req, uint8_t *dest, const char *str, int dest_len, size_t flags) +{ + size_t len; + unsigned int grow_size; + uint8_t *buf0; + const int max_bytes_per_char = 3; + + if (!(flags & (STR_ASCII|STR_UNICODE))) { + flags |= (req->flags2 & FLAGS2_UNICODE_STRINGS) ? STR_UNICODE : STR_ASCII; + } + + if (dest == NULL) { + dest = req->out.data + req->out.data_size; + } + + if (dest_len != -1) { + len = dest_len; + } else { + len = (strlen(str)+2) * max_bytes_per_char; + } + + grow_size = len + PTR_DIFF(dest, req->out.data); + buf0 = req->out.buffer; + + req_grow_allocation(req, grow_size); + + if (buf0 != req->out.buffer) { + dest = req->out.buffer + PTR_DIFF(dest, buf0); + } + + len = push_string(dest, str, len, flags); + + grow_size = len + PTR_DIFF(dest, req->out.data); + + if (grow_size > req->out.data_size) { + req_grow_data(req, grow_size); + } + + return len; +} + +/* + append raw bytes into the data portion of the request packet + return the number of bytes added +*/ +size_t req_append_bytes(struct smbsrv_request *req, + const uint8_t *bytes, size_t byte_len) +{ + req_grow_allocation(req, byte_len + req->out.data_size); + memcpy(req->out.data + req->out.data_size, bytes, byte_len); + req_grow_data(req, byte_len + req->out.data_size); + return byte_len; +} +/* + append variable block (type 5 buffer) into the data portion of the request packet + return the number of bytes added +*/ +size_t req_append_var_block(struct smbsrv_request *req, + const uint8_t *bytes, uint16_t byte_len) +{ + req_grow_allocation(req, byte_len + 3 + req->out.data_size); + SCVAL(req->out.data + req->out.data_size, 0, 5); + SSVAL(req->out.data + req->out.data_size, 1, byte_len); /* add field length */ + if (byte_len > 0) { + memcpy(req->out.data + req->out.data_size + 3, bytes, byte_len); + } + req_grow_data(req, byte_len + 3 + req->out.data_size); + return byte_len + 3; +} +/** + pull a UCS2 string from a request packet, returning a talloced unix string + + the string length is limited by the 3 things: + - the data size in the request (end of packet) + - the passed 'byte_len' if it is not -1 + - the end of string (null termination) + + Note that 'byte_len' is the number of bytes in the packet + + on failure zero is returned and *dest is set to NULL, otherwise the number + of bytes consumed in the packet is returned +*/ +static size_t req_pull_ucs2(struct request_bufinfo *bufinfo, const char **dest, const uint8_t *src, int byte_len, unsigned int flags) +{ + int src_len, src_len2, alignment=0; + bool ret; + char *dest2; + size_t converted_size = 0; + + if (!(flags & STR_NOALIGN) && ucs2_align(bufinfo->align_base, src, flags)) { + src++; + alignment=1; + if (byte_len != -1) { + byte_len--; + } + } + + if (flags & STR_NO_RANGE_CHECK) { + src_len = byte_len; + } else { + src_len = bufinfo->data_size - PTR_DIFF(src, bufinfo->data); + if (byte_len != -1 && src_len > byte_len) { + src_len = byte_len; + } + } + + if (src_len < 0) { + *dest = NULL; + return 0; + } + + src_len2 = utf16_len_n(src, src_len); + if (src_len2 == 0) { + *dest = talloc_strdup(bufinfo->mem_ctx, ""); + return src_len2 + alignment; + } + + ret = convert_string_talloc(bufinfo->mem_ctx, CH_UTF16, CH_UNIX, src, src_len2, (void **)&dest2, &converted_size); + + if (!ret) { + *dest = NULL; + return 0; + } + *dest = dest2; + + return src_len2 + alignment; +} + +/** + pull a ascii string from a request packet, returning a talloced string + + the string length is limited by the 3 things: + - the data size in the request (end of packet) + - the passed 'byte_len' if it is not -1 + - the end of string (null termination) + + Note that 'byte_len' is the number of bytes in the packet + + on failure zero is returned and *dest is set to NULL, otherwise the number + of bytes consumed in the packet is returned +*/ +static size_t req_pull_ascii(struct request_bufinfo *bufinfo, const char **dest, const uint8_t *src, int byte_len, unsigned int flags) +{ + int src_len, src_len2; + bool ret; + char *dest2; + size_t converted_size = 0; + + if (flags & STR_NO_RANGE_CHECK) { + src_len = byte_len; + } else { + src_len = bufinfo->data_size - PTR_DIFF(src, bufinfo->data); + if (src_len < 0) { + *dest = NULL; + return 0; + } + if (byte_len != -1 && src_len > byte_len) { + src_len = byte_len; + } + } + + src_len2 = strnlen((const char *)src, src_len); + if (src_len2 <= src_len - 1) { + /* include the termination if we didn't reach the end of the packet */ + src_len2++; + } + + ret = convert_string_talloc(bufinfo->mem_ctx, CH_DOS, CH_UNIX, src, src_len2, (void **)&dest2, &converted_size); + + if (!ret) { + *dest = NULL; + return 0; + } + *dest = dest2; + + return src_len2; +} + +/** + pull a string from a request packet, returning a talloced string + + the string length is limited by the 3 things: + - the data size in the request (end of packet) + - the passed 'byte_len' if it is not -1 + - the end of string (null termination) + + Note that 'byte_len' is the number of bytes in the packet + + on failure zero is returned and *dest is set to NULL, otherwise the number + of bytes consumed in the packet is returned +*/ +size_t req_pull_string(struct request_bufinfo *bufinfo, const char **dest, const uint8_t *src, int byte_len, unsigned int flags) +{ + if (!(flags & STR_ASCII) && + (((flags & STR_UNICODE) || (bufinfo->flags & BUFINFO_FLAG_UNICODE)))) { + return req_pull_ucs2(bufinfo, dest, src, byte_len, flags); + } + + return req_pull_ascii(bufinfo, dest, src, byte_len, flags); +} + + +/** + pull a ASCII4 string buffer from a request packet, returning a talloced string + + an ASCII4 buffer is a null terminated string that has a prefix + of the character 0x4. It tends to be used in older parts of the protocol. + + on failure *dest is set to the zero length string. This seems to + match win2000 behaviour +*/ +size_t req_pull_ascii4(struct request_bufinfo *bufinfo, const char **dest, const uint8_t *src, unsigned int flags) +{ + ssize_t ret; + + if (PTR_DIFF(src, bufinfo->data) + 1 > bufinfo->data_size) { + /* win2000 treats this as the empty string! */ + (*dest) = talloc_strdup(bufinfo->mem_ctx, ""); + return 0; + } + + /* this consumes the 0x4 byte. We don't check whether the byte + is actually 0x4 or not. This matches win2000 server + behaviour */ + src++; + + ret = req_pull_string(bufinfo, dest, src, -1, flags); + if (ret == -1) { + (*dest) = talloc_strdup(bufinfo->mem_ctx, ""); + return 1; + } + + return ret + 1; +} + +/** + pull a DATA_BLOB from a request packet, returning a talloced blob + + return false if any part is outside the data portion of the packet +*/ +bool req_pull_blob(struct request_bufinfo *bufinfo, const uint8_t *src, int len, DATA_BLOB *blob) +{ + if (len != 0 && req_data_oob(bufinfo, src, len)) { + return false; + } + + (*blob) = data_blob_talloc(bufinfo->mem_ctx, src, len); + + return true; +} + +/* check that a lump of data in a request is within the bounds of the data section of + the packet */ +bool req_data_oob(struct request_bufinfo *bufinfo, const uint8_t *ptr, uint32_t count) +{ + if (count == 0) { + return false; + } + + /* be careful with wraparound! */ + if ((uintptr_t)ptr < (uintptr_t)bufinfo->data || + (uintptr_t)ptr >= (uintptr_t)bufinfo->data + bufinfo->data_size || + count > bufinfo->data_size || + (uintptr_t)ptr + count > (uintptr_t)bufinfo->data + bufinfo->data_size) { + return true; + } + return false; +} + + +/* + pull an open file handle from a packet, taking account of the chained_fnum +*/ +static uint16_t req_fnum(struct smbsrv_request *req, const uint8_t *base, unsigned int offset) +{ + if (req->chained_fnum != -1) { + return req->chained_fnum; + } + return SVAL(base, offset); +} + +struct ntvfs_handle *smbsrv_pull_fnum(struct smbsrv_request *req, const uint8_t *base, unsigned int offset) +{ + struct smbsrv_handle *handle; + uint16_t fnum = req_fnum(req, base, offset); + + handle = smbsrv_smb_handle_find(req->tcon, fnum, req->request_time); + if (!handle) { + return NULL; + } + + /* + * For SMB tcons and sessions can be mixed! + * But we need to make sure that file handles + * are only accessed by the opening session! + * + * So check if the handle is valid for the given session! + */ + if (handle->session != req->session) { + return NULL; + } + + return handle->ntvfs; +} + +void smbsrv_push_fnum(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); + SSVAL(base, offset, handle->hid); +} + +NTSTATUS smbsrv_handle_create_new(void *private_data, struct ntvfs_request *ntvfs, struct ntvfs_handle **_h) +{ + struct smbsrv_request *req = talloc_get_type(ntvfs->frontend_data.private_data, + struct smbsrv_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; +} + +NTSTATUS smbsrv_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; +} + +void smbsrv_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); +} + +struct ntvfs_handle *smbsrv_handle_search_by_wire_key(void *private_data, struct ntvfs_request *ntvfs, const DATA_BLOB *key) +{ + struct smbsrv_request *req = talloc_get_type(ntvfs->frontend_data.private_data, + struct smbsrv_request); + + if (key->length != 2) return NULL; + + return smbsrv_pull_fnum(req, key->data, 0); +} + +DATA_BLOB smbsrv_handle_get_wire_key(void *private_data, struct ntvfs_handle *handle, TALLOC_CTX *mem_ctx) +{ + uint8_t key[2]; + + smbsrv_push_fnum(key, 0, handle); + + return data_blob_talloc(mem_ctx, key, sizeof(key)); +} diff --git a/source4/smb_server/smb/search.c b/source4/smb_server/smb/search.c new file mode 100644 index 0000000..bf47217 --- /dev/null +++ b/source4/smb_server/smb/search.c @@ -0,0 +1,283 @@ +/* + Unix SMB/CIFS implementation. + SMBsearch handling + Copyright (C) Andrew Tridgell 2003 + Copyright (C) James J Myers 2003 <myersjj@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +/* + This file handles the parsing of transact2 requests +*/ + +#include "includes.h" +#include "smb_server/smb_server.h" +#include "ntvfs/ntvfs.h" + + +/* a structure to encapsulate the state information about + * an in-progress search first/next operation */ +struct search_state { + struct smbsrv_request *req; + union smb_search_data *file; + uint16_t last_entry_offset; +}; + +/* + fill a single entry in a search find reply +*/ +static bool find_fill_info(struct smbsrv_request *req, + const union smb_search_data *file) +{ + uint8_t *p; + + if (req->out.data_size + 43 > req_max_data(req)) { + return false; + } + + req_grow_data(req, req->out.data_size + 43); + p = req->out.data + req->out.data_size - 43; + + SCVAL(p, 0, file->search.id.reserved); + memcpy(p+1, file->search.id.name, 11); + SCVAL(p, 12, file->search.id.handle); + SIVAL(p, 13, file->search.id.server_cookie); + SIVAL(p, 17, file->search.id.client_cookie); + SCVAL(p, 21, file->search.attrib); + srv_push_dos_date(req->smb_conn, p, 22, file->search.write_time); + SIVAL(p, 26, file->search.size); + memset(p+30, ' ', 12); + memcpy(p+30, file->search.name, MIN(strlen(file->search.name)+1, 12)); + SCVAL(p,42,0); + + return true; +} + +/* callback function for search first/next */ +static bool find_callback(void *private_data, const union smb_search_data *file) +{ + struct search_state *state = (struct search_state *)private_data; + + return find_fill_info(state->req, file); +} + +/**************************************************************************** + Reply to a search first (async reply) +****************************************************************************/ +static void reply_search_first_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_search_first *sf; + + SMBSRV_CHECK_ASYNC_STATUS(sf, union smb_search_first); + + SSVAL(req->out.vwv, VWV(0), sf->search_first.out.count); + + smbsrv_send_reply(req); +} + +/**************************************************************************** + Reply to a search next (async reply) +****************************************************************************/ +static void reply_search_next_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + union smb_search_next *sn; + + SMBSRV_CHECK_ASYNC_STATUS(sn, union smb_search_next); + + SSVAL(req->out.vwv, VWV(0), sn->search_next.out.count); + + smbsrv_send_reply(req); +} + +/**************************************************************************** + Reply to a search. +****************************************************************************/ +void smbsrv_reply_search(struct smbsrv_request *req) +{ + union smb_search_first *sf; + uint16_t resume_key_length; + struct search_state *state; + uint8_t *p; + enum smb_search_level level = RAW_SEARCH_SEARCH; + uint8_t op = CVAL(req->in.hdr,HDR_COM); + + if (op == SMBffirst) { + level = RAW_SEARCH_FFIRST; + } else if (op == SMBfunique) { + level = RAW_SEARCH_FUNIQUE; + } + + /* parse request */ + if (req->in.wct != 2) { + smbsrv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + + SMBSRV_TALLOC_IO_PTR(sf, union smb_search_first); + + p = req->in.data; + p += req_pull_ascii4(&req->in.bufinfo, &sf->search_first.in.pattern, + p, STR_TERMINATE); + if (!sf->search_first.in.pattern) { + smbsrv_send_error(req, NT_STATUS_OBJECT_NAME_NOT_FOUND); + return; + } + + if (req_data_oob(&req->in.bufinfo, p, 3)) { + smbsrv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + if (*p != 5) { + smbsrv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + resume_key_length = SVAL(p, 1); + p += 3; + + /* setup state for callback */ + state = talloc(req, struct search_state); + if (!state) { + smbsrv_send_error(req, NT_STATUS_NO_MEMORY); + return; + } + + state->req = req; + state->file = NULL; + state->last_entry_offset = 0; + + /* construct reply */ + smbsrv_setup_reply(req, 1, 0); + SSVAL(req->out.vwv, VWV(0), 0); + req_append_var_block(req, NULL, 0); + + if (resume_key_length != 0) { + union smb_search_next *sn; + + if (resume_key_length != 21 || + req_data_oob(&req->in.bufinfo, p, 21) || + level == RAW_SEARCH_FUNIQUE) { + smbsrv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + + /* do a search next operation */ + SMBSRV_TALLOC_IO_PTR(sn, union smb_search_next); + SMBSRV_SETUP_NTVFS_REQUEST(reply_search_next_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + sn->search_next.in.id.reserved = CVAL(p, 0); + memcpy(sn->search_next.in.id.name, p+1, 11); + sn->search_next.in.id.handle = CVAL(p, 12); + sn->search_next.in.id.server_cookie = IVAL(p, 13); + sn->search_next.in.id.client_cookie = IVAL(p, 17); + + sn->search_next.level = level; + sn->search_next.data_level = RAW_SEARCH_DATA_SEARCH; + sn->search_next.in.max_count = SVAL(req->in.vwv, VWV(0)); + sn->search_next.in.search_attrib = SVAL(req->in.vwv, VWV(1)); + + /* call backend */ + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_search_next(req->ntvfs, sn, state, find_callback)); + } else { + SMBSRV_SETUP_NTVFS_REQUEST(reply_search_first_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + /* do a search first operation */ + sf->search_first.level = level; + sf->search_first.data_level = RAW_SEARCH_DATA_SEARCH; + sf->search_first.in.search_attrib = SVAL(req->in.vwv, VWV(1)); + sf->search_first.in.max_count = SVAL(req->in.vwv, VWV(0)); + + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_search_first(req->ntvfs, sf, state, find_callback)); + } +} + + +/**************************************************************************** + Reply to a fclose (async reply) +****************************************************************************/ +static void reply_fclose_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + + SMBSRV_CHECK_ASYNC_STATUS_SIMPLE; + + /* construct reply */ + smbsrv_setup_reply(req, 1, 0); + + SSVAL(req->out.vwv, VWV(0), 0); + + smbsrv_send_reply(req); +} + + +/**************************************************************************** + Reply to fclose (stop directory search). +****************************************************************************/ +void smbsrv_reply_fclose(struct smbsrv_request *req) +{ + union smb_search_close *sc; + uint16_t resume_key_length; + uint8_t *p; + const char *pattern; + + /* parse request */ + if (req->in.wct != 2) { + smbsrv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + + SMBSRV_TALLOC_IO_PTR(sc, union smb_search_close); + SMBSRV_SETUP_NTVFS_REQUEST(reply_fclose_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + p = req->in.data; + p += req_pull_ascii4(&req->in.bufinfo, &pattern, p, STR_TERMINATE); + if (pattern && *pattern) { + smbsrv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + + if (req_data_oob(&req->in.bufinfo, p, 3)) { + smbsrv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + if (*p != 5) { + smbsrv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + resume_key_length = SVAL(p, 1); + p += 3; + + if (resume_key_length != 21) { + smbsrv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + + if (req_data_oob(&req->in.bufinfo, p, 21)) { + smbsrv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + + sc->fclose.level = RAW_FINDCLOSE_FCLOSE; + sc->fclose.in.max_count = SVAL(req->in.vwv, VWV(0)); + sc->fclose.in.search_attrib = SVAL(req->in.vwv, VWV(1)); + sc->fclose.in.id.reserved = CVAL(p, 0); + memcpy(sc->fclose.in.id.name, p+1, 11); + sc->fclose.in.id.handle = CVAL(p, 12); + sc->fclose.in.id.server_cookie = IVAL(p, 13); + sc->fclose.in.id.client_cookie = IVAL(p, 17); + + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_search_close(req->ntvfs, sc)); + +} diff --git a/source4/smb_server/smb/service.c b/source4/smb_server/smb/service.c new file mode 100644 index 0000000..eb1874c --- /dev/null +++ b/source4/smb_server/smb/service.c @@ -0,0 +1,202 @@ +/* + Unix SMB/CIFS implementation. + service (connection) handling + Copyright (C) Andrew Tridgell 1992-2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "smb_server/smb_server.h" +#include "samba/service_stream.h" +#include "ntvfs/ntvfs.h" +#include "param/param.h" + +#undef strcasecmp + +/**************************************************************************** + Make a connection, given the snum to connect to, and the vuser of the + connecting user if appropriate. + Does note invoke the NTVFS connection hook +****************************************************************************/ +static NTSTATUS make_connection_scfg(struct smbsrv_request *req, + struct share_config *scfg, + enum ntvfs_type type, + DATA_BLOB password, + const char *dev) +{ + struct smbsrv_tcon *tcon; + NTSTATUS status; + uint64_t ntvfs_caps = 0; + + tcon = smbsrv_smb_tcon_new(req->smb_conn, scfg->name); + if (!tcon) { + DEBUG(0,("Couldn't find free connection.\n")); + return NT_STATUS_INSUFFICIENT_RESOURCES; + } + req->tcon = tcon; + + if (req->smb_conn->negotiate.client_caps & CAP_LEVEL_II_OPLOCKS) { + 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, ("make_connection_scfg: connection failed for service %s\n", + scfg->name)); + goto failed; + } + + status = ntvfs_set_oplock_handler(tcon->ntvfs, smbsrv_send_oplock_break, tcon); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("make_connection: 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,("make_connection: NTVFS failed to set the addresses!\n")); + goto failed; + } + + status = ntvfs_set_handle_callbacks(tcon->ntvfs, + smbsrv_handle_create_new, + smbsrv_handle_make_valid, + smbsrv_handle_destroy, + smbsrv_handle_search_by_wire_key, + smbsrv_handle_get_wire_key, + tcon); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("make_connection: NTVFS failed to set the handle callbacks!\n")); + goto failed; + } + + return NT_STATUS_OK; + +failed: + req->tcon = NULL; + talloc_free(tcon); + return status; +} + +/**************************************************************************** + Make a connection to a service. + * + * @param service +****************************************************************************/ +static NTSTATUS make_connection(struct smbsrv_request *req, + const char *service, DATA_BLOB password, + const char *dev) +{ + NTSTATUS status; + enum ntvfs_type type; + const char *type_str; + struct share_config *scfg; + char *sharetype; + + /* the service might be of the form \\SERVER\SHARE. Should we put + the server name we get from this somewhere? */ + if (strncmp(service, "\\\\", 2) == 0) { + 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,("make_connection: couldn't find service %s: %s\n", service, nt_errstr(status))); + return NT_STATUS_BAD_NETWORK_NAME; + } + + /* TODO: check the password, when it's share level security! */ + + 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, "type", "DISK"); + if (sharetype && strcmp(sharetype, "IPC") == 0) { + type = NTVFS_IPC; + type_str = "IPC"; + } else if (sharetype && strcmp(sharetype, "PRINTER") == 0) { + type = NTVFS_PRINT; + type_str = "LPT:"; + } else { + type = NTVFS_DISK; + type_str = "A:"; + } + TALLOC_FREE(sharetype); + + if (strcmp(dev, "?????") != 0 && strcasecmp(type_str, dev) != 0) { + /* the client gave us the wrong device type */ + return NT_STATUS_BAD_DEVICE_TYPE; + } + + return make_connection_scfg(req, scfg, type, password, dev); +} + +/* + backend for tree connect call, in preparation for calling ntvfs_connect() +*/ +NTSTATUS smbsrv_tcon_backend(struct smbsrv_request *req, union smb_tcon *con) +{ + NTSTATUS status; + + if (con->generic.level == RAW_TCON_TCON) { + DATA_BLOB password; + password = data_blob_string_const(con->tcon.in.password); + + status = make_connection(req, con->tcon.in.service, password, con->tcon.in.dev); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + con->tcon.out.max_xmit = req->smb_conn->negotiate.max_recv; + con->tcon.out.tid = req->tcon->tid; + + return status; + } + + /* TODO: take a look at tconx.in.flags! */ + + status = make_connection(req, con->tconx.in.path, con->tconx.in.password, + con->tconx.in.device); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + con->tconx.out.tid = req->tcon->tid; + con->tconx.out.options = SMB_SUPPORT_SEARCH_BITS | (share_int_option(req->tcon->ntvfs->config, SHARE_CSC_POLICY, SHARE_CSC_POLICY_DEFAULT) << 2); + if (share_bool_option(req->tcon->ntvfs->config, SHARE_MSDFS_ROOT, SHARE_MSDFS_ROOT_DEFAULT) && lpcfg_host_msdfs(req->smb_conn->lp_ctx)) { + con->tconx.out.options |= SMB_SHARE_IN_DFS; + } + + return status; +} diff --git a/source4/smb_server/smb/sesssetup.c b/source4/smb_server/smb/sesssetup.c new file mode 100644 index 0000000..07a7e7e --- /dev/null +++ b/source4/smb_server/smb/sesssetup.c @@ -0,0 +1,644 @@ + +/* + Unix SMB/CIFS implementation. + handle SMBsessionsetup + Copyright (C) Andrew Tridgell 1998-2001 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2005 + Copyright (C) Jim McDonough 2002 + Copyright (C) Luke Howard 2003 + 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 "version.h" +#include "auth/gensec/gensec.h" +#include "auth/auth.h" +#include "smb_server/smb_server.h" +#include "samba/service_stream.h" +#include "param/param.h" +#include "../lib/tsocket/tsocket.h" +#include "lib/stream/packet.h" + +struct sesssetup_context { + struct auth4_context *auth_context; + struct smbsrv_request *req; +}; + +/* + * Log the SMB authentication, as by not calling GENSEC we won't log + * it during the gensec_session_info(). + */ +void smbsrv_not_spengo_sesssetup_authz_log(struct smbsrv_request *req, + struct auth_session_info *session_info) +{ + struct tsocket_address *local_address; + struct tsocket_address *remote_address; + TALLOC_CTX *frame = talloc_stackframe(); + + remote_address = socket_get_remote_addr(req->smb_conn->connection->socket, + frame); + local_address = socket_get_local_addr(req->smb_conn->connection->socket, + frame); + + log_successful_authz_event(req->smb_conn->connection->msg_ctx, + req->smb_conn->lp_ctx, + remote_address, + local_address, + "SMB", + "bare-NTLM", + AUTHZ_TRANSPORT_PROTECTION_SMB, + session_info); + + talloc_free(frame); + return; +} + + +/* + setup the OS, Lanman and domain portions of a session setup reply +*/ +static void sesssetup_common_strings(struct smbsrv_request *req, + char **os, char **lanman, char **domain) +{ + (*os) = talloc_asprintf(req, "Unix"); + (*lanman) = talloc_asprintf(req, "Samba %s", SAMBA_VERSION_STRING); + (*domain) = talloc_asprintf(req, "%s", + lpcfg_workgroup(req->smb_conn->lp_ctx)); +} + +static void smbsrv_sesssetup_backend_send(struct smbsrv_request *req, + union smb_sesssetup *sess, + NTSTATUS status) +{ + if (NT_STATUS_IS_OK(status)) { + req->smb_conn->negotiate.done_sesssetup = true; + /* we need to keep the session long term */ + req->session = talloc_steal(req->smb_conn, req->session); + } + smbsrv_reply_sesssetup_send(req, sess, status); +} + +static void sesssetup_old_send(struct tevent_req *subreq) +{ + struct sesssetup_context *state = tevent_req_callback_data(subreq, struct sesssetup_context); + struct smbsrv_request *req = state->req; + + union smb_sesssetup *sess = talloc_get_type(req->io_ptr, union smb_sesssetup); + struct auth_user_info_dc *user_info_dc = NULL; + struct auth_session_info *session_info; + struct smbsrv_session *smb_sess; + NTSTATUS status; + uint8_t authoritative = 1; + uint32_t flags; + + status = auth_check_password_recv(subreq, req, &user_info_dc, + &authoritative); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) goto failed; + + flags = AUTH_SESSION_INFO_DEFAULT_GROUPS; + if (user_info_dc->info->authenticated) { + flags |= AUTH_SESSION_INFO_AUTHENTICATED; + } + /* This references user_info_dc into session_info */ + status = req->smb_conn->negotiate.auth_context->generate_session_info(req->smb_conn->negotiate.auth_context, + req, + user_info_dc, sess->old.in.user, + flags, &session_info); + if (!NT_STATUS_IS_OK(status)) goto failed; + + /* allocate a new session */ + smb_sess = smbsrv_session_new(req->smb_conn, req, NULL); + if (!smb_sess) { + status = NT_STATUS_INSUFFICIENT_RESOURCES; + goto failed; + } + + smbsrv_not_spengo_sesssetup_authz_log(req, session_info); + + /* 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; + + /* To correctly process any AndX packet (like a tree connect) + * we need to fill in the session on the request here */ + req->session = smb_sess; + sess->old.out.vuid = smb_sess->vuid; + +failed: + status = nt_status_squash(status); + smbsrv_sesssetup_backend_send(req, sess, status); +} + +/* + handler for old style session setup +*/ +static void sesssetup_old(struct smbsrv_request *req, union smb_sesssetup *sess) +{ + struct auth_usersupplied_info *user_info = NULL; + struct tsocket_address *remote_address, *local_address; + const char *remote_machine = NULL; + struct tevent_req *subreq; + struct sesssetup_context *state; + + sess->old.out.vuid = 0; + sess->old.out.action = 0; + + sesssetup_common_strings(req, + &sess->old.out.os, + &sess->old.out.lanman, + &sess->old.out.domain); + + if (!req->smb_conn->negotiate.done_sesssetup) { + req->smb_conn->negotiate.max_send = sess->old.in.bufsize; + } + + if (req->smb_conn->negotiate.calling_name) { + remote_machine = req->smb_conn->negotiate.calling_name->name; + } + + remote_address = socket_get_remote_addr(req->smb_conn->connection->socket, req); + if (!remote_address) goto nomem; + + if (!remote_machine) { + remote_machine = tsocket_address_inet_addr_string(remote_address, req); + if (!remote_machine) goto nomem; + } + + local_address = socket_get_local_addr(req->smb_conn->connection->socket, req); + if (!local_address) goto nomem; + + user_info = talloc_zero(req, struct auth_usersupplied_info); + if (!user_info) goto nomem; + + user_info->service_description = "SMB"; + + user_info->logon_parameters = 0; + user_info->flags = 0; + user_info->client.account_name = sess->old.in.user; + user_info->client.domain_name = sess->old.in.domain; + user_info->workstation_name = remote_machine; + + user_info->remote_host = talloc_steal(user_info, remote_address); + user_info->local_host = talloc_steal(user_info, local_address); + + user_info->password_state = AUTH_PASSWORD_RESPONSE; + user_info->password.response.lanman = sess->old.in.password; + user_info->password.response.lanman.data = talloc_steal(user_info, sess->old.in.password.data); + user_info->password.response.nt = data_blob(NULL, 0); + + state = talloc(req, struct sesssetup_context); + if (!state) goto nomem; + + if (req->smb_conn->negotiate.auth_context) { + state->auth_context = req->smb_conn->negotiate.auth_context; + } else { + /* TODO: should we use just "anonymous" here? */ + NTSTATUS status = auth_context_create(state, + req->smb_conn->connection->event.ctx, + req->smb_conn->connection->msg_ctx, + req->smb_conn->lp_ctx, + &state->auth_context); + if (!NT_STATUS_IS_OK(status)) { + smbsrv_sesssetup_backend_send(req, sess, status); + return; + } + } + + state->req = req; + + subreq = auth_check_password_send(state, + req->smb_conn->connection->event.ctx, + req->smb_conn->negotiate.auth_context, + user_info); + if (!subreq) goto nomem; + tevent_req_set_callback(subreq, sesssetup_old_send, state); + return; + +nomem: + smbsrv_sesssetup_backend_send(req, sess, NT_STATUS_NO_MEMORY); +} + +static void sesssetup_nt1_send(struct tevent_req *subreq) +{ + struct sesssetup_context *state = tevent_req_callback_data(subreq, struct sesssetup_context); + struct smbsrv_request *req = state->req; + union smb_sesssetup *sess = talloc_get_type(req->io_ptr, union smb_sesssetup); + struct auth_user_info_dc *user_info_dc = NULL; + struct auth_session_info *session_info; + struct smbsrv_session *smb_sess; + uint8_t authoritative = 1; + uint32_t flags; + NTSTATUS status; + + status = auth_check_password_recv(subreq, req, &user_info_dc, + &authoritative); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) goto failed; + + flags = AUTH_SESSION_INFO_DEFAULT_GROUPS; + if (user_info_dc->info->authenticated) { + flags |= AUTH_SESSION_INFO_AUTHENTICATED; + } + /* This references user_info_dc into session_info */ + status = state->auth_context->generate_session_info(state->auth_context, + req, + user_info_dc, + sess->nt1.in.user, + flags, + &session_info); + if (!NT_STATUS_IS_OK(status)) goto failed; + + /* allocate a new session */ + smb_sess = smbsrv_session_new(req->smb_conn, req, NULL); + if (!smb_sess) { + status = NT_STATUS_INSUFFICIENT_RESOURCES; + goto failed; + } + + smbsrv_not_spengo_sesssetup_authz_log(req, session_info); + + /* 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; + + /* To correctly process any AndX packet (like a tree connect) + * we need to fill in the session on the request here */ + req->session = smb_sess; + sess->nt1.out.vuid = smb_sess->vuid; + + if (!smbsrv_setup_signing(req->smb_conn, &session_info->session_key, &sess->nt1.in.password2)) { + /* Already signing, or disabled */ + goto done; + } + +done: + status = NT_STATUS_OK; +failed: + status = nt_status_squash(status); + smbsrv_sesssetup_backend_send(req, sess, status); +} + +/* + handler for NT1 style session setup +*/ +static void sesssetup_nt1(struct smbsrv_request *req, union smb_sesssetup *sess) +{ + NTSTATUS status; + struct auth_usersupplied_info *user_info = NULL; + struct tsocket_address *remote_address, *local_address; + const char *remote_machine = NULL; + struct tevent_req *subreq; + struct sesssetup_context *state; + bool allow_raw = lpcfg_raw_ntlmv2_auth(req->smb_conn->lp_ctx); + + sess->nt1.out.vuid = 0; + sess->nt1.out.action = 0; + + sesssetup_common_strings(req, + &sess->nt1.out.os, + &sess->nt1.out.lanman, + &sess->nt1.out.domain); + + if (!req->smb_conn->negotiate.done_sesssetup) { + req->smb_conn->negotiate.max_send = sess->nt1.in.bufsize; + req->smb_conn->negotiate.client_caps = sess->nt1.in.capabilities; + } + + state = talloc(req, struct sesssetup_context); + if (!state) goto nomem; + + state->req = req; + + if (req->smb_conn->negotiate.oid) { + if (sess->nt1.in.user && *sess->nt1.in.user) { + /* We can't accept a normal login, because we + * don't have a challenge */ + status = NT_STATUS_LOGON_FAILURE; + goto failed; + } + + /* TODO: should we use just "anonymous" here? */ + status = auth_context_create(state, + req->smb_conn->connection->event.ctx, + req->smb_conn->connection->msg_ctx, + req->smb_conn->lp_ctx, + &state->auth_context); + if (!NT_STATUS_IS_OK(status)) goto failed; + } else if (req->smb_conn->negotiate.auth_context) { + state->auth_context = req->smb_conn->negotiate.auth_context; + } else { + /* TODO: should we use just "anonymous" here? */ + status = auth_context_create(state, + req->smb_conn->connection->event.ctx, + req->smb_conn->connection->msg_ctx, + req->smb_conn->lp_ctx, + &state->auth_context); + if (!NT_STATUS_IS_OK(status)) goto failed; + } + + if (req->smb_conn->negotiate.calling_name) { + remote_machine = req->smb_conn->negotiate.calling_name->name; + } + + remote_address = socket_get_remote_addr(req->smb_conn->connection->socket, req); + if (!remote_address) goto nomem; + + if (!remote_machine) { + remote_machine = tsocket_address_inet_addr_string(remote_address, req); + if (!remote_machine) goto nomem; + } + + local_address = socket_get_local_addr(req->smb_conn->connection->socket, req); + if (!local_address) goto nomem; + + user_info = talloc_zero(req, struct auth_usersupplied_info); + if (!user_info) goto nomem; + + user_info->service_description = "SMB"; + user_info->auth_description = "bare-NTLM"; + + user_info->logon_parameters = 0; + user_info->flags = 0; + user_info->client.account_name = sess->nt1.in.user; + user_info->client.domain_name = sess->nt1.in.domain; + user_info->workstation_name = remote_machine; + user_info->remote_host = talloc_steal(user_info, remote_address); + user_info->local_host = talloc_steal(user_info, local_address); + + user_info->password_state = AUTH_PASSWORD_RESPONSE; + user_info->password.response.lanman = sess->nt1.in.password1; + user_info->password.response.lanman.data = talloc_steal(user_info, sess->nt1.in.password1.data); + user_info->password.response.nt = sess->nt1.in.password2; + user_info->password.response.nt.data = talloc_steal(user_info, sess->nt1.in.password2.data); + + if (!allow_raw && user_info->password.response.nt.length >= 48) { + /* + * NTLMv2_RESPONSE has at least 48 bytes + * and should only be supported via NTLMSSP. + */ + status = NT_STATUS_INVALID_PARAMETER; + goto failed; + } + + subreq = auth_check_password_send(state, + req->smb_conn->connection->event.ctx, + state->auth_context, + user_info); + if (!subreq) goto nomem; + tevent_req_set_callback(subreq, sesssetup_nt1_send, state); + + return; + +nomem: + status = NT_STATUS_NO_MEMORY; +failed: + status = nt_status_squash(status); + smbsrv_sesssetup_backend_send(req, sess, status); +} + +struct sesssetup_spnego_state { + struct smbsrv_request *req; + union smb_sesssetup *sess; + struct smbsrv_session *smb_sess; +}; + +static void sesssetup_spnego_send(struct tevent_req *subreq) +{ + struct sesssetup_spnego_state *s = tevent_req_callback_data(subreq, + struct sesssetup_spnego_state); + struct smbsrv_request *req = s->req; + union smb_sesssetup *sess = s->sess; + struct smbsrv_session *smb_sess = s->smb_sess; + struct auth_session_info *session_info = NULL; + NTSTATUS status; + NTSTATUS skey_status; + DATA_BLOB session_key; + + status = gensec_update_recv(subreq, req, &sess->spnego.out.secblob); + packet_recv_enable(req->smb_conn->packet); + 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; + + /* The session_key is only needed until the end of the smbsrv_setup_signing() call */ + skey_status = gensec_session_key(smb_sess->gensec_ctx, req, &session_key); + if (NT_STATUS_IS_OK(skey_status)) { + smbsrv_setup_signing(req->smb_conn, &session_key, NULL); + } + + /* 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; + +done: + sess->spnego.out.vuid = smb_sess->vuid; +failed: + status = nt_status_squash(status); + smbsrv_sesssetup_backend_send(req, sess, status); + if (!NT_STATUS_IS_OK(status) && + !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + talloc_free(smb_sess); + } +} + +/* + handler for SPNEGO style session setup +*/ +static void sesssetup_spnego(struct smbsrv_request *req, union smb_sesssetup *sess) +{ + NTSTATUS status; + struct smbsrv_session *smb_sess = NULL; + bool is_smb_sess_new = false; + struct sesssetup_spnego_state *s = NULL; + uint16_t vuid; + struct tevent_req *subreq; + + sess->spnego.out.vuid = 0; + sess->spnego.out.action = 0; + + sesssetup_common_strings(req, + &sess->spnego.out.os, + &sess->spnego.out.lanman, + &sess->spnego.out.workgroup); + + if (!req->smb_conn->negotiate.done_sesssetup) { + req->smb_conn->negotiate.max_send = sess->spnego.in.bufsize; + req->smb_conn->negotiate.client_caps = sess->spnego.in.capabilities; + } + + vuid = SVAL(req->in.hdr,HDR_UID); + + /* lookup an existing 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, + "SMB"); + + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Failed to set service description"); + goto failed; + } + + status = gensec_start_mech_by_oid(gensec_ctx, req->smb_conn->negotiate.oid); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to start GENSEC %s server code: %s\n", + gensec_get_name_by_oid(gensec_ctx, req->smb_conn->negotiate.oid), 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; + } + is_smb_sess_new = true; + } else { + smb_sess = smbsrv_session_find_sesssetup(req->smb_conn, vuid); + } + + if (!smb_sess) { + status = NT_STATUS_DOS(ERRSRV, ERRbaduid); + goto failed; + } + + if (smb_sess->session_info) { + status = NT_STATUS_INVALID_PARAMETER; + 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; + } + + s = talloc(req, struct sesssetup_spnego_state); + if (!s) goto nomem; + s->req = req; + s->sess = sess; + s->smb_sess = smb_sess; + + subreq = gensec_update_send(s, + req->smb_conn->connection->event.ctx, + smb_sess->gensec_ctx, + sess->spnego.in.secblob); + if (!subreq) { + goto nomem; + } + /* 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); + tevent_req_set_callback(subreq, sesssetup_spnego_send, s); + + return; + +nomem: + status = NT_STATUS_NO_MEMORY; +failed: + if (is_smb_sess_new) { + talloc_free(smb_sess); + } + status = nt_status_squash(status); + smbsrv_sesssetup_backend_send(req, sess, status); +} + +/* + backend for sessionsetup call - this takes all 3 variants of the call +*/ +void smbsrv_sesssetup_backend(struct smbsrv_request *req, + union smb_sesssetup *sess) +{ + switch (sess->old.level) { + case RAW_SESSSETUP_OLD: + sesssetup_old(req, sess); + return; + + case RAW_SESSSETUP_NT1: + sesssetup_nt1(req, sess); + return; + + case RAW_SESSSETUP_SPNEGO: + sesssetup_spnego(req, sess); + return; + + case RAW_SESSSETUP_SMB2: + break; + } + + smbsrv_sesssetup_backend_send(req, sess, NT_STATUS_INVALID_LEVEL); +} diff --git a/source4/smb_server/smb/signing.c b/source4/smb_server/smb/signing.c new file mode 100644 index 0000000..3fe7cff --- /dev/null +++ b/source4/smb_server/smb/signing.c @@ -0,0 +1,147 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Andrew Tridgell 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "smb_server/smb_server.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "param/param.h" + + +/* + sign an outgoing packet +*/ +void smbsrv_sign_packet(struct smbsrv_request *req) +{ +#if 0 + /* enable this when packet signing is preventing you working out why valgrind + says that data is uninitialised */ + file_save("pkt.dat", req->out.buffer, req->out.size); +#endif + + switch (req->smb_conn->signing.signing_state) { + case SMB_SIGNING_ENGINE_OFF: + break; + + case SMB_SIGNING_ENGINE_BSRSPYL: + /* mark the packet as signed - BEFORE we sign it...*/ + mark_packet_signed(&req->out); + + /* I wonder what BSRSPYL stands for - but this is what MS + actually sends! */ + memcpy((req->out.hdr + HDR_SS_FIELD), "BSRSPYL ", 8); + break; + + case SMB_SIGNING_ENGINE_ON: + + sign_outgoing_message(&req->out, + &req->smb_conn->signing.mac_key, + req->seq_num+1); + break; + } + return; +} + + + +/* + setup the signing key for a connection. Called after authentication succeeds + in a session setup +*/ +bool smbsrv_setup_signing(struct smbsrv_connection *smb_conn, + DATA_BLOB *session_key, + DATA_BLOB *response) +{ + if (!set_smb_signing_common(&smb_conn->signing)) { + return false; + } + return smbcli_simple_set_signing(smb_conn, + &smb_conn->signing, session_key, response); +} + +bool smbsrv_init_signing(struct smbsrv_connection *smb_conn) +{ + smb_conn->signing.mac_key = data_blob(NULL, 0); + if (!smbcli_set_signing_off(&smb_conn->signing)) { + return false; + } + + smb_conn->signing.allow_smb_signing + = lpcfg_server_signing_allowed(smb_conn->lp_ctx, + &smb_conn->signing.mandatory_signing); + return true; +} + +/* + allocate a sequence number to a request +*/ +static void req_signing_alloc_seq_num(struct smbsrv_request *req) +{ + req->seq_num = req->smb_conn->signing.next_seq_num; + + if (req->smb_conn->signing.signing_state != SMB_SIGNING_ENGINE_OFF) { + req->smb_conn->signing.next_seq_num += 2; + } +} + +/* + called for requests that do not produce a reply of their own +*/ +void smbsrv_signing_no_reply(struct smbsrv_request *req) +{ + if (req->smb_conn->signing.signing_state != SMB_SIGNING_ENGINE_OFF) { + req->smb_conn->signing.next_seq_num--; + } +} + +/*********************************************************** + SMB signing - Simple implementation - check a MAC sent by client +************************************************************/ +/** + * Check a packet supplied by the server. + * @return false if we had an established signing connection + * which had a back checksum, true otherwise + */ +bool smbsrv_signing_check_incoming(struct smbsrv_request *req) +{ + bool good; + + req_signing_alloc_seq_num(req); + + switch (req->smb_conn->signing.signing_state) + { + case SMB_SIGNING_ENGINE_OFF: + return true; + case SMB_SIGNING_ENGINE_BSRSPYL: + case SMB_SIGNING_ENGINE_ON: + { + if (req->in.size < (HDR_SS_FIELD + 8)) { + return false; + } else { + good = check_signed_incoming_message(&req->in, + &req->smb_conn->signing.mac_key, + req->seq_num); + + return signing_good(&req->smb_conn->signing, + req->seq_num+1, good); + } + } + } + return false; +} diff --git a/source4/smb_server/smb/srvtime.c b/source4/smb_server/smb/srvtime.c new file mode 100644 index 0000000..7aee907 --- /dev/null +++ b/source4/smb_server/smb/srvtime.c @@ -0,0 +1,82 @@ +/* + Unix SMB/CIFS implementation. + + server side time handling + + Copyright (C) Andrew Tridgell 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "smb_server/smb_server.h" + + +/******************************************************************* +put a dos date into a buffer (time/date format) +This takes GMT time and puts local time for zone_offset in the buffer +********************************************************************/ +void srv_push_dos_date(struct smbsrv_connection *smb_server, + uint8_t *buf, int offset, time_t unixdate) +{ + push_dos_date(buf, offset, unixdate, smb_server->negotiate.zone_offset); +} + +/******************************************************************* +put a dos date into a buffer (date/time format) +This takes GMT time and puts local time in the buffer +********************************************************************/ +void srv_push_dos_date2(struct smbsrv_connection *smb_server, + uint8_t *buf, int offset, time_t unixdate) +{ + push_dos_date2(buf, offset, unixdate, smb_server->negotiate.zone_offset); +} + +/******************************************************************* +put a dos 32 bit "unix like" date into a buffer. This routine takes +GMT and converts it to LOCAL time in zone_offset before putting it +********************************************************************/ +void srv_push_dos_date3(struct smbsrv_connection *smb_server, + uint8_t *buf, int offset, time_t unixdate) +{ + push_dos_date3(buf, offset, unixdate, smb_server->negotiate.zone_offset); +} + +/******************************************************************* +convert a dos date +********************************************************************/ +time_t srv_pull_dos_date(struct smbsrv_connection *smb_server, + const uint8_t *date_ptr) +{ + return pull_dos_date(date_ptr, smb_server->negotiate.zone_offset); +} + +/******************************************************************* +like srv_pull_dos_date() but the words are reversed +********************************************************************/ +time_t srv_pull_dos_date2(struct smbsrv_connection *smb_server, + const uint8_t *date_ptr) +{ + return pull_dos_date2(date_ptr, smb_server->negotiate.zone_offset); +} + +/******************************************************************* + create a unix GMT date from a dos date in 32 bit "unix like" format + these arrive in server zone, with corresponding DST + ******************************************************************/ +time_t srv_pull_dos_date3(struct smbsrv_connection *smb_server, + const uint8_t *date_ptr) +{ + return pull_dos_date3(date_ptr, smb_server->negotiate.zone_offset); +} diff --git a/source4/smb_server/smb/trans2.c b/source4/smb_server/smb/trans2.c new file mode 100644 index 0000000..fc357ed --- /dev/null +++ b/source4/smb_server/smb/trans2.c @@ -0,0 +1,1562 @@ +/* + Unix SMB/CIFS implementation. + transaction2 handling + Copyright (C) Andrew Tridgell 2003 + Copyright Matthieu Patou <mat@matws.net> 2010-2011 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +/* + This file handles the parsing of transact2 requests +*/ + +#include "includes.h" +#include "samba/service_stream.h" +#include "smb_server/smb_server.h" +#include "ntvfs/ntvfs.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "librpc/gen_ndr/dfsblobs.h" +#include "librpc/gen_ndr/ndr_dfsblobs.h" +#include "dsdb/samdb/samdb.h" +#include "auth/session.h" +#include "param/param.h" +#include "lib/tsocket/tsocket.h" +#include "dfs_server/dfs_server_ad.h" + +#define MAX_DFS_RESPONSE 56*1024 /* 56 Kb */ + +#define TRANS2_CHECK_ASYNC_STATUS_SIMPLE do { \ + if (!NT_STATUS_IS_OK(req->ntvfs->async_states->status)) { \ + trans2_setup_reply(trans, 0, 0, 0);\ + return req->ntvfs->async_states->status; \ + } \ +} while (0) +#define TRANS2_CHECK_ASYNC_STATUS(ptr, type) do { \ + TRANS2_CHECK_ASYNC_STATUS_SIMPLE; \ + ptr = talloc_get_type(op->op_info, type); \ +} while (0) +#define TRANS2_CHECK(cmd) do { \ + NTSTATUS _status; \ + _status = cmd; \ + NT_STATUS_NOT_OK_RETURN(_status); \ +} while (0) + +/* + hold the state of a nttrans op while in progress. Needed to allow for async backend + functions. +*/ +struct trans_op { + struct smbsrv_request *req; + struct smb_trans2 *trans; + uint8_t command; + NTSTATUS (*send_fn)(struct trans_op *); + void *op_info; +}; + +#define CHECK_MIN_BLOB_SIZE(blob, size) do { \ + if ((blob)->length < (size)) { \ + return NT_STATUS_INFO_LENGTH_MISMATCH; \ + }} while (0) + +/* setup a trans2 reply, given the data and params sizes */ +static NTSTATUS trans2_setup_reply(struct smb_trans2 *trans, + uint16_t param_size, uint16_t data_size, + uint8_t setup_count) +{ + trans->out.setup_count = setup_count; + if (setup_count > 0) { + trans->out.setup = talloc_zero_array(trans, uint16_t, setup_count); + NT_STATUS_HAVE_NO_MEMORY(trans->out.setup); + } + trans->out.params = data_blob_talloc(trans, NULL, param_size); + if (param_size > 0) NT_STATUS_HAVE_NO_MEMORY(trans->out.params.data); + + trans->out.data = data_blob_talloc(trans, NULL, data_size); + if (data_size > 0) NT_STATUS_HAVE_NO_MEMORY(trans->out.data.data); + + return NT_STATUS_OK; +} + +static NTSTATUS trans2_push_fsinfo(struct smbsrv_connection *smb_conn, + TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, + union smb_fsinfo *fsinfo, + int default_str_flags) +{ + enum smb_fsinfo_level passthru_level; + + switch (fsinfo->generic.level) { + case RAW_QFS_ALLOCATION: + TRANS2_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 18)); + + SIVAL(blob->data, 0, fsinfo->allocation.out.fs_id); + SIVAL(blob->data, 4, fsinfo->allocation.out.sectors_per_unit); + SIVAL(blob->data, 8, fsinfo->allocation.out.total_alloc_units); + SIVAL(blob->data, 12, fsinfo->allocation.out.avail_alloc_units); + SSVAL(blob->data, 16, fsinfo->allocation.out.bytes_per_sector); + + return NT_STATUS_OK; + + case RAW_QFS_VOLUME: + TRANS2_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 5)); + + SIVAL(blob->data, 0, fsinfo->volume.out.serial_number); + /* w2k3 implements this incorrectly for unicode - it + * leaves the last byte off the string */ + TRANS2_CHECK(smbsrv_blob_append_string(mem_ctx, blob, + fsinfo->volume.out.volume_name.s, + 4, default_str_flags, + STR_LEN8BIT|STR_NOALIGN)); + + return NT_STATUS_OK; + + case RAW_QFS_VOLUME_INFO: + passthru_level = RAW_QFS_VOLUME_INFORMATION; + break; + + case RAW_QFS_SIZE_INFO: + passthru_level = RAW_QFS_SIZE_INFORMATION; + break; + + case RAW_QFS_DEVICE_INFO: + passthru_level = RAW_QFS_DEVICE_INFORMATION; + break; + + case RAW_QFS_ATTRIBUTE_INFO: + passthru_level = RAW_QFS_ATTRIBUTE_INFORMATION; + break; + + default: + passthru_level = fsinfo->generic.level; + break; + } + + return smbsrv_push_passthru_fsinfo(mem_ctx, blob, + passthru_level, fsinfo, + default_str_flags); +} + +/* + trans2 qfsinfo implementation send +*/ +static NTSTATUS trans2_qfsinfo_send(struct trans_op *op) +{ + struct smbsrv_request *req = op->req; + struct smb_trans2 *trans = op->trans; + union smb_fsinfo *fsinfo; + + TRANS2_CHECK_ASYNC_STATUS(fsinfo, union smb_fsinfo); + + TRANS2_CHECK(trans2_setup_reply(trans, 0, 0, 0)); + + TRANS2_CHECK(trans2_push_fsinfo(req->smb_conn, trans, + &trans->out.data, fsinfo, + SMBSRV_REQ_DEFAULT_STR_FLAGS(req))); + + return NT_STATUS_OK; +} + +/* + trans2 qfsinfo implementation +*/ +static NTSTATUS trans2_qfsinfo(struct smbsrv_request *req, struct trans_op *op) +{ + struct smb_trans2 *trans = op->trans; + union smb_fsinfo *fsinfo; + uint16_t level; + + /* make sure we got enough parameters */ + if (trans->in.params.length != 2) { + return NT_STATUS_FOOBAR; + } + + fsinfo = talloc(op, union smb_fsinfo); + NT_STATUS_HAVE_NO_MEMORY(fsinfo); + + level = SVAL(trans->in.params.data, 0); + + /* work out the backend level - we make it 1-1 in the header */ + fsinfo->generic.level = (enum smb_fsinfo_level)level; + if (fsinfo->generic.level >= RAW_QFS_GENERIC) { + return NT_STATUS_INVALID_LEVEL; + } + + op->op_info = fsinfo; + op->send_fn = trans2_qfsinfo_send; + + return ntvfs_fsinfo(req->ntvfs, fsinfo); +} + + +/* + trans2 open implementation send +*/ +static NTSTATUS trans2_open_send(struct trans_op *op) +{ + struct smbsrv_request *req = op->req; + struct smb_trans2 *trans = op->trans; + union smb_open *io; + + TRANS2_CHECK_ASYNC_STATUS(io, union smb_open); + + TRANS2_CHECK(trans2_setup_reply(trans, 30, 0, 0)); + + smbsrv_push_fnum(trans->out.params.data, VWV(0), io->t2open.out.file.ntvfs); + SSVAL(trans->out.params.data, VWV(1), io->t2open.out.attrib); + srv_push_dos_date3(req->smb_conn, trans->out.params.data, + VWV(2), io->t2open.out.write_time); + SIVAL(trans->out.params.data, VWV(4), io->t2open.out.size); + SSVAL(trans->out.params.data, VWV(6), io->t2open.out.access); + SSVAL(trans->out.params.data, VWV(7), io->t2open.out.ftype); + SSVAL(trans->out.params.data, VWV(8), io->t2open.out.devstate); + SSVAL(trans->out.params.data, VWV(9), io->t2open.out.action); + SIVAL(trans->out.params.data, VWV(10), 0); /* reserved */ + SSVAL(trans->out.params.data, VWV(12), 0); /* EaErrorOffset */ + SIVAL(trans->out.params.data, VWV(13), 0); /* EaLength */ + + return NT_STATUS_OK; +} + +/* + trans2 open implementation +*/ +static NTSTATUS trans2_open(struct smbsrv_request *req, struct trans_op *op) +{ + struct smb_trans2 *trans = op->trans; + union smb_open *io; + + /* make sure we got enough parameters */ + if (trans->in.params.length < 29) { + return NT_STATUS_FOOBAR; + } + + io = talloc(op, union smb_open); + NT_STATUS_HAVE_NO_MEMORY(io); + + io->t2open.level = RAW_OPEN_T2OPEN; + io->t2open.in.flags = SVAL(trans->in.params.data, VWV(0)); + io->t2open.in.open_mode = SVAL(trans->in.params.data, VWV(1)); + io->t2open.in.search_attrs = SVAL(trans->in.params.data, VWV(2)); + io->t2open.in.file_attrs = SVAL(trans->in.params.data, VWV(3)); + io->t2open.in.write_time = srv_pull_dos_date(req->smb_conn, + trans->in.params.data + VWV(4)); + io->t2open.in.open_func = SVAL(trans->in.params.data, VWV(6)); + io->t2open.in.size = IVAL(trans->in.params.data, VWV(7)); + io->t2open.in.timeout = IVAL(trans->in.params.data, VWV(9)); + io->t2open.in.num_eas = 0; + io->t2open.in.eas = NULL; + + smbsrv_blob_pull_string(&req->in.bufinfo, &trans->in.params, 28, &io->t2open.in.fname, 0); + if (io->t2open.in.fname == NULL) { + return NT_STATUS_FOOBAR; + } + + TRANS2_CHECK(ea_pull_list(&trans->in.data, io, &io->t2open.in.num_eas, &io->t2open.in.eas)); + + op->op_info = io; + op->send_fn = trans2_open_send; + + return ntvfs_open(req->ntvfs, io); +} + + +/* + trans2 simple send +*/ +static NTSTATUS trans2_simple_send(struct trans_op *op) +{ + struct smbsrv_request *req = op->req; + struct smb_trans2 *trans = op->trans; + + TRANS2_CHECK_ASYNC_STATUS_SIMPLE; + + TRANS2_CHECK(trans2_setup_reply(trans, 2, 0, 0)); + + SSVAL(trans->out.params.data, VWV(0), 0); + + return NT_STATUS_OK; +} + +/* + trans2 mkdir implementation +*/ +static NTSTATUS trans2_mkdir(struct smbsrv_request *req, struct trans_op *op) +{ + struct smb_trans2 *trans = op->trans; + union smb_mkdir *io; + + /* make sure we got enough parameters */ + if (trans->in.params.length < 5) { + return NT_STATUS_FOOBAR; + } + + io = talloc(op, union smb_mkdir); + NT_STATUS_HAVE_NO_MEMORY(io); + + io->t2mkdir.level = RAW_MKDIR_T2MKDIR; + smbsrv_blob_pull_string(&req->in.bufinfo, &trans->in.params, 4, &io->t2mkdir.in.path, 0); + if (io->t2mkdir.in.path == NULL) { + return NT_STATUS_FOOBAR; + } + + TRANS2_CHECK(ea_pull_list(&trans->in.data, io, + &io->t2mkdir.in.num_eas, + &io->t2mkdir.in.eas)); + + op->op_info = io; + op->send_fn = trans2_simple_send; + + return ntvfs_mkdir(req->ntvfs, io); +} + +static NTSTATUS trans2_push_fileinfo(struct smbsrv_connection *smb_conn, + TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, + union smb_fileinfo *st, + int default_str_flags) +{ + uint32_t list_size; + enum smb_fileinfo_level passthru_level; + + switch (st->generic.level) { + case RAW_FILEINFO_GENERIC: + case RAW_FILEINFO_GETATTR: + case RAW_FILEINFO_GETATTRE: + case RAW_FILEINFO_SEC_DESC: + case RAW_FILEINFO_SMB2_ALL_EAS: + case RAW_FILEINFO_SMB2_ALL_INFORMATION: + /* handled elsewhere */ + return NT_STATUS_INVALID_LEVEL; + + case RAW_FILEINFO_UNIX_BASIC: + case RAW_FILEINFO_UNIX_LINK: + /* not implemented yet */ + return NT_STATUS_INVALID_LEVEL; + + case RAW_FILEINFO_STANDARD: + TRANS2_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 22)); + + srv_push_dos_date2(smb_conn, blob->data, 0, st->standard.out.create_time); + srv_push_dos_date2(smb_conn, blob->data, 4, st->standard.out.access_time); + srv_push_dos_date2(smb_conn, blob->data, 8, st->standard.out.write_time); + SIVAL(blob->data, 12, st->standard.out.size); + SIVAL(blob->data, 16, st->standard.out.alloc_size); + SSVAL(blob->data, 20, st->standard.out.attrib); + return NT_STATUS_OK; + + case RAW_FILEINFO_EA_SIZE: + TRANS2_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, 26)); + + srv_push_dos_date2(smb_conn, blob->data, 0, st->ea_size.out.create_time); + srv_push_dos_date2(smb_conn, blob->data, 4, st->ea_size.out.access_time); + srv_push_dos_date2(smb_conn, blob->data, 8, st->ea_size.out.write_time); + SIVAL(blob->data, 12, st->ea_size.out.size); + SIVAL(blob->data, 16, st->ea_size.out.alloc_size); + SSVAL(blob->data, 20, st->ea_size.out.attrib); + SIVAL(blob->data, 22, st->ea_size.out.ea_size); + return NT_STATUS_OK; + + case RAW_FILEINFO_EA_LIST: + list_size = ea_list_size(st->ea_list.out.num_eas, + st->ea_list.out.eas); + TRANS2_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, list_size)); + + ea_put_list(blob->data, + st->ea_list.out.num_eas, st->ea_list.out.eas); + return NT_STATUS_OK; + + case RAW_FILEINFO_ALL_EAS: + list_size = ea_list_size(st->all_eas.out.num_eas, + st->all_eas.out.eas); + TRANS2_CHECK(smbsrv_blob_grow_data(mem_ctx, blob, list_size)); + + ea_put_list(blob->data, + st->all_eas.out.num_eas, st->all_eas.out.eas); + return NT_STATUS_OK; + + case RAW_FILEINFO_IS_NAME_VALID: + return NT_STATUS_OK; + + case RAW_FILEINFO_BASIC_INFO: + passthru_level = RAW_FILEINFO_BASIC_INFORMATION; + break; + + case RAW_FILEINFO_STANDARD_INFO: + passthru_level = RAW_FILEINFO_STANDARD_INFORMATION; + break; + + case RAW_FILEINFO_EA_INFO: + passthru_level = RAW_FILEINFO_EA_INFORMATION; + break; + + case RAW_FILEINFO_COMPRESSION_INFO: + passthru_level = RAW_FILEINFO_COMPRESSION_INFORMATION; + break; + + case RAW_FILEINFO_ALL_INFO: + passthru_level = RAW_FILEINFO_ALL_INFORMATION; + break; + + case RAW_FILEINFO_NAME_INFO: + passthru_level = RAW_FILEINFO_NAME_INFORMATION; + break; + + case RAW_FILEINFO_ALT_NAME_INFO: + passthru_level = RAW_FILEINFO_ALT_NAME_INFORMATION; + break; + + case RAW_FILEINFO_STREAM_INFO: + passthru_level = RAW_FILEINFO_STREAM_INFORMATION; + break; + + default: + passthru_level = st->generic.level; + break; + } + + return smbsrv_push_passthru_fileinfo(mem_ctx, blob, + passthru_level, st, + default_str_flags); +} + +/* + fill in the reply from a qpathinfo or qfileinfo call +*/ +static NTSTATUS trans2_fileinfo_send(struct trans_op *op) +{ + struct smbsrv_request *req = op->req; + struct smb_trans2 *trans = op->trans; + union smb_fileinfo *st; + + TRANS2_CHECK_ASYNC_STATUS(st, union smb_fileinfo); + + TRANS2_CHECK(trans2_setup_reply(trans, 2, 0, 0)); + SSVAL(trans->out.params.data, 0, 0); + + TRANS2_CHECK(trans2_push_fileinfo(req->smb_conn, trans, + &trans->out.data, st, + SMBSRV_REQ_DEFAULT_STR_FLAGS(req))); + + return NT_STATUS_OK; +} + +/* + trans2 qpathinfo implementation +*/ +static NTSTATUS trans2_qpathinfo(struct smbsrv_request *req, struct trans_op *op) +{ + struct smb_trans2 *trans = op->trans; + union smb_fileinfo *st; + uint16_t level; + + /* make sure we got enough parameters */ + if (trans->in.params.length < 2) { + return NT_STATUS_FOOBAR; + } + + st = talloc(op, union smb_fileinfo); + NT_STATUS_HAVE_NO_MEMORY(st); + + level = SVAL(trans->in.params.data, 0); + + smbsrv_blob_pull_string(&req->in.bufinfo, &trans->in.params, 6, &st->generic.in.file.path, 0); + if (st->generic.in.file.path == NULL) { + return NT_STATUS_FOOBAR; + } + + /* work out the backend level - we make it 1-1 in the header */ + st->generic.level = (enum smb_fileinfo_level)level; + if (st->generic.level >= RAW_FILEINFO_GENERIC) { + return NT_STATUS_INVALID_LEVEL; + } + + if (st->generic.level == RAW_FILEINFO_EA_LIST) { + TRANS2_CHECK(ea_pull_name_list(&trans->in.data, req, + &st->ea_list.in.num_names, + &st->ea_list.in.ea_names)); + } + + op->op_info = st; + op->send_fn = trans2_fileinfo_send; + + return ntvfs_qpathinfo(req->ntvfs, st); +} + + +/* + trans2 qpathinfo implementation +*/ +static NTSTATUS trans2_qfileinfo(struct smbsrv_request *req, struct trans_op *op) +{ + struct smb_trans2 *trans = op->trans; + union smb_fileinfo *st; + uint16_t level; + struct ntvfs_handle *h; + + /* make sure we got enough parameters */ + if (trans->in.params.length < 4) { + return NT_STATUS_FOOBAR; + } + + st = talloc(op, union smb_fileinfo); + NT_STATUS_HAVE_NO_MEMORY(st); + + h = smbsrv_pull_fnum(req, trans->in.params.data, 0); + level = SVAL(trans->in.params.data, 2); + + st->generic.in.file.ntvfs = h; + /* work out the backend level - we make it 1-1 in the header */ + st->generic.level = (enum smb_fileinfo_level)level; + if (st->generic.level >= RAW_FILEINFO_GENERIC) { + return NT_STATUS_INVALID_LEVEL; + } + + if (st->generic.level == RAW_FILEINFO_EA_LIST) { + TRANS2_CHECK(ea_pull_name_list(&trans->in.data, req, + &st->ea_list.in.num_names, + &st->ea_list.in.ea_names)); + } + + op->op_info = st; + op->send_fn = trans2_fileinfo_send; + + SMBSRV_CHECK_FILE_HANDLE_NTSTATUS(st->generic.in.file.ntvfs); + return ntvfs_qfileinfo(req->ntvfs, st); +} + + +/* + parse a trans2 setfileinfo/setpathinfo data blob +*/ +static NTSTATUS trans2_parse_sfileinfo(struct smbsrv_request *req, + union smb_setfileinfo *st, + const DATA_BLOB *blob) +{ + enum smb_setfileinfo_level passthru_level; + + switch (st->generic.level) { + case RAW_SFILEINFO_GENERIC: + case RAW_SFILEINFO_SETATTR: + case RAW_SFILEINFO_SETATTRE: + case RAW_SFILEINFO_SEC_DESC: + /* handled elsewhere */ + return NT_STATUS_INVALID_LEVEL; + + case RAW_SFILEINFO_STANDARD: + CHECK_MIN_BLOB_SIZE(blob, 12); + + st->standard.in.create_time = srv_pull_dos_date2(req->smb_conn, blob->data + 0); + st->standard.in.access_time = srv_pull_dos_date2(req->smb_conn, blob->data + 4); + st->standard.in.write_time = srv_pull_dos_date2(req->smb_conn, blob->data + 8); + + return NT_STATUS_OK; + + case RAW_SFILEINFO_EA_SET: + return ea_pull_list(blob, req, + &st->ea_set.in.num_eas, + &st->ea_set.in.eas); + + case SMB_SFILEINFO_BASIC_INFO: + case SMB_SFILEINFO_BASIC_INFORMATION: + passthru_level = SMB_SFILEINFO_BASIC_INFORMATION; + break; + + case SMB_SFILEINFO_DISPOSITION_INFO: + case SMB_SFILEINFO_DISPOSITION_INFORMATION: + passthru_level = SMB_SFILEINFO_DISPOSITION_INFORMATION; + break; + + case SMB_SFILEINFO_ALLOCATION_INFO: + case SMB_SFILEINFO_ALLOCATION_INFORMATION: + passthru_level = SMB_SFILEINFO_ALLOCATION_INFORMATION; + break; + + case RAW_SFILEINFO_END_OF_FILE_INFO: + case RAW_SFILEINFO_END_OF_FILE_INFORMATION: + passthru_level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; + break; + + case RAW_SFILEINFO_RENAME_INFORMATION: + case RAW_SFILEINFO_POSITION_INFORMATION: + case RAW_SFILEINFO_MODE_INFORMATION: + passthru_level = st->generic.level; + break; + + case RAW_SFILEINFO_UNIX_BASIC: + case RAW_SFILEINFO_UNIX_LINK: + case RAW_SFILEINFO_UNIX_HLINK: + case RAW_SFILEINFO_PIPE_INFORMATION: + case RAW_SFILEINFO_VALID_DATA_INFORMATION: + case RAW_SFILEINFO_SHORT_NAME_INFORMATION: + case RAW_SFILEINFO_1025: + case RAW_SFILEINFO_1027: + case RAW_SFILEINFO_1029: + case RAW_SFILEINFO_1030: + case RAW_SFILEINFO_1031: + case RAW_SFILEINFO_1032: + case RAW_SFILEINFO_1036: + case RAW_SFILEINFO_1041: + case RAW_SFILEINFO_1042: + case RAW_SFILEINFO_1043: + case RAW_SFILEINFO_1044: + return NT_STATUS_INVALID_LEVEL; + + default: + /* we need a default here to cope with invalid values on the wire */ + return NT_STATUS_INVALID_LEVEL; + } + + return smbsrv_pull_passthru_sfileinfo(st, passthru_level, st, + blob, SMBSRV_REQ_DEFAULT_STR_FLAGS(req), + &req->in.bufinfo); +} + +/* + trans2 setfileinfo implementation +*/ +static NTSTATUS trans2_setfileinfo(struct smbsrv_request *req, struct trans_op *op) +{ + struct smb_trans2 *trans = op->trans; + union smb_setfileinfo *st; + uint16_t level; + struct ntvfs_handle *h; + + /* make sure we got enough parameters */ + if (trans->in.params.length < 4) { + return NT_STATUS_FOOBAR; + } + + st = talloc(op, union smb_setfileinfo); + NT_STATUS_HAVE_NO_MEMORY(st); + + h = smbsrv_pull_fnum(req, trans->in.params.data, 0); + level = SVAL(trans->in.params.data, 2); + + st->generic.in.file.ntvfs = h; + /* work out the backend level - we make it 1-1 in the header */ + st->generic.level = (enum smb_setfileinfo_level)level; + if (st->generic.level >= RAW_SFILEINFO_GENERIC) { + return NT_STATUS_INVALID_LEVEL; + } + + TRANS2_CHECK(trans2_parse_sfileinfo(req, st, &trans->in.data)); + + op->op_info = st; + op->send_fn = trans2_simple_send; + + SMBSRV_CHECK_FILE_HANDLE_NTSTATUS(st->generic.in.file.ntvfs); + return ntvfs_setfileinfo(req->ntvfs, st); +} + +/* + trans2 setpathinfo implementation +*/ +static NTSTATUS trans2_setpathinfo(struct smbsrv_request *req, struct trans_op *op) +{ + struct smb_trans2 *trans = op->trans; + union smb_setfileinfo *st; + uint16_t level; + + /* make sure we got enough parameters */ + if (trans->in.params.length < 4) { + return NT_STATUS_FOOBAR; + } + + st = talloc(op, union smb_setfileinfo); + NT_STATUS_HAVE_NO_MEMORY(st); + + level = SVAL(trans->in.params.data, 0); + + smbsrv_blob_pull_string(&req->in.bufinfo, &trans->in.params, 6, &st->generic.in.file.path, 0); + if (st->generic.in.file.path == NULL) { + return NT_STATUS_FOOBAR; + } + + /* work out the backend level - we make it 1-1 in the header */ + st->generic.level = (enum smb_setfileinfo_level)level; + if (st->generic.level >= RAW_SFILEINFO_GENERIC) { + return NT_STATUS_INVALID_LEVEL; + } + + TRANS2_CHECK(trans2_parse_sfileinfo(req, st, &trans->in.data)); + + op->op_info = st; + op->send_fn = trans2_simple_send; + + return ntvfs_setpathinfo(req->ntvfs, st); +} + + +/* a structure to encapsulate the state information about an in-progress ffirst/fnext operation */ +struct find_state { + struct trans_op *op; + void *search; + enum smb_search_data_level data_level; + uint16_t last_entry_offset; + uint16_t flags; +}; + +/* + fill a single entry in a trans2 find reply +*/ +static NTSTATUS find_fill_info(struct find_state *state, + const union smb_search_data *file) +{ + struct smbsrv_request *req = state->op->req; + struct smb_trans2 *trans = state->op->trans; + uint8_t *data; + unsigned int ofs = trans->out.data.length; + uint32_t ea_size; + + switch (state->data_level) { + case RAW_SEARCH_DATA_GENERIC: + case RAW_SEARCH_DATA_SEARCH: + /* handled elsewhere */ + return NT_STATUS_INVALID_LEVEL; + + case RAW_SEARCH_DATA_STANDARD: + if (state->flags & FLAG_TRANS2_FIND_REQUIRE_RESUME) { + TRANS2_CHECK(smbsrv_blob_grow_data(trans, &trans->out.data, ofs + 27)); + SIVAL(trans->out.data.data, ofs, file->standard.resume_key); + ofs += 4; + } else { + TRANS2_CHECK(smbsrv_blob_grow_data(trans, &trans->out.data, ofs + 23)); + } + data = trans->out.data.data + ofs; + srv_push_dos_date2(req->smb_conn, data, 0, file->standard.create_time); + srv_push_dos_date2(req->smb_conn, data, 4, file->standard.access_time); + srv_push_dos_date2(req->smb_conn, data, 8, file->standard.write_time); + SIVAL(data, 12, file->standard.size); + SIVAL(data, 16, file->standard.alloc_size); + SSVAL(data, 20, file->standard.attrib); + TRANS2_CHECK(smbsrv_blob_append_string(trans, &trans->out.data, file->standard.name.s, + ofs + 22, SMBSRV_REQ_DEFAULT_STR_FLAGS(req), + STR_LEN8BIT | STR_TERMINATE | STR_LEN_NOTERM)); + break; + + case RAW_SEARCH_DATA_EA_SIZE: + if (state->flags & FLAG_TRANS2_FIND_REQUIRE_RESUME) { + TRANS2_CHECK(smbsrv_blob_grow_data(trans, &trans->out.data, ofs + 31)); + SIVAL(trans->out.data.data, ofs, file->ea_size.resume_key); + ofs += 4; + } else { + TRANS2_CHECK(smbsrv_blob_grow_data(trans, &trans->out.data, ofs + 27)); + } + data = trans->out.data.data + ofs; + srv_push_dos_date2(req->smb_conn, data, 0, file->ea_size.create_time); + srv_push_dos_date2(req->smb_conn, data, 4, file->ea_size.access_time); + srv_push_dos_date2(req->smb_conn, data, 8, file->ea_size.write_time); + SIVAL(data, 12, file->ea_size.size); + SIVAL(data, 16, file->ea_size.alloc_size); + SSVAL(data, 20, file->ea_size.attrib); + SIVAL(data, 22, file->ea_size.ea_size); + TRANS2_CHECK(smbsrv_blob_append_string(trans, &trans->out.data, file->ea_size.name.s, + ofs + 26, SMBSRV_REQ_DEFAULT_STR_FLAGS(req), + STR_LEN8BIT | STR_NOALIGN)); + TRANS2_CHECK(smbsrv_blob_fill_data(trans, &trans->out.data, trans->out.data.length + 1)); + break; + + case RAW_SEARCH_DATA_EA_LIST: + ea_size = ea_list_size(file->ea_list.eas.num_eas, file->ea_list.eas.eas); + if (state->flags & FLAG_TRANS2_FIND_REQUIRE_RESUME) { + TRANS2_CHECK(smbsrv_blob_grow_data(trans, &trans->out.data, ofs + 27 + ea_size)); + SIVAL(trans->out.data.data, ofs, file->ea_list.resume_key); + ofs += 4; + } else { + TRANS2_CHECK(smbsrv_blob_grow_data(trans, &trans->out.data, ofs + 23 + ea_size)); + } + data = trans->out.data.data + ofs; + srv_push_dos_date2(req->smb_conn, data, 0, file->ea_list.create_time); + srv_push_dos_date2(req->smb_conn, data, 4, file->ea_list.access_time); + srv_push_dos_date2(req->smb_conn, data, 8, file->ea_list.write_time); + SIVAL(data, 12, file->ea_list.size); + SIVAL(data, 16, file->ea_list.alloc_size); + SSVAL(data, 20, file->ea_list.attrib); + ea_put_list(data+22, file->ea_list.eas.num_eas, file->ea_list.eas.eas); + TRANS2_CHECK(smbsrv_blob_append_string(trans, &trans->out.data, file->ea_list.name.s, + ofs + 22 + ea_size, SMBSRV_REQ_DEFAULT_STR_FLAGS(req), + STR_LEN8BIT | STR_NOALIGN)); + TRANS2_CHECK(smbsrv_blob_fill_data(trans, &trans->out.data, trans->out.data.length + 1)); + break; + + case RAW_SEARCH_DATA_DIRECTORY_INFO: + case RAW_SEARCH_DATA_FULL_DIRECTORY_INFO: + case RAW_SEARCH_DATA_NAME_INFO: + case RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO: + case RAW_SEARCH_DATA_ID_FULL_DIRECTORY_INFO: + case RAW_SEARCH_DATA_ID_BOTH_DIRECTORY_INFO: + return smbsrv_push_passthru_search(trans, &trans->out.data, state->data_level, file, + SMBSRV_REQ_DEFAULT_STR_FLAGS(req)); + + case RAW_SEARCH_DATA_UNIX_INFO: + case RAW_SEARCH_DATA_UNIX_INFO2: + return NT_STATUS_INVALID_LEVEL; + } + + return NT_STATUS_OK; +} + +/* callback function for trans2 findfirst/findnext */ +static bool find_callback(void *private_data, const union smb_search_data *file) +{ + struct find_state *state = talloc_get_type(private_data, struct find_state); + struct smb_trans2 *trans = state->op->trans; + unsigned int old_length; + + old_length = trans->out.data.length; + + if (!NT_STATUS_IS_OK(find_fill_info(state, file)) || + trans->out.data.length > trans->in.max_data) { + /* restore the old length and tell the backend to stop */ + smbsrv_blob_grow_data(trans, &trans->out.data, old_length); + return false; + } + + state->last_entry_offset = old_length; + return true; +} + +/* + trans2 findfirst send + */ +static NTSTATUS trans2_findfirst_send(struct trans_op *op) +{ + struct smbsrv_request *req = op->req; + struct smb_trans2 *trans = op->trans; + union smb_search_first *search; + struct find_state *state; + uint8_t *param; + + TRANS2_CHECK_ASYNC_STATUS(state, struct find_state); + search = talloc_get_type(state->search, union smb_search_first); + + /* fill in the findfirst reply header */ + param = trans->out.params.data; + SSVAL(param, VWV(0), search->t2ffirst.out.handle); + SSVAL(param, VWV(1), search->t2ffirst.out.count); + SSVAL(param, VWV(2), search->t2ffirst.out.end_of_search); + SSVAL(param, VWV(3), 0); + SSVAL(param, VWV(4), state->last_entry_offset); + + return NT_STATUS_OK; +} + +/* + trans2 getdfsreferral implementation +*/ +static NTSTATUS trans2_getdfsreferral(struct smbsrv_request *req, + struct trans_op *op) +{ + enum ndr_err_code ndr_err; + struct smb_trans2 *trans = op->trans; + struct ldb_context *ldb; + struct loadparm_context *lp_ctx; + NTSTATUS status; + struct dfs_GetDFSReferral *r; + DATA_BLOB outblob = data_blob_null; + uint16_t nb_referrals = 0; + + lp_ctx = req->tcon->ntvfs->lp_ctx; + if (!lpcfg_host_msdfs(lp_ctx)) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + r = talloc_zero(req, struct dfs_GetDFSReferral); + NT_STATUS_HAVE_NO_MEMORY(r); + + ldb = samdb_connect(r, + req->tcon->ntvfs->event_ctx, + lp_ctx, + system_session(lp_ctx), + NULL, + 0); + if (ldb == NULL) { + DEBUG(2,(__location__ ": Failed to open samdb\n")); + talloc_free(r); + return NT_STATUS_INTERNAL_ERROR; + } + + ndr_err = ndr_pull_struct_blob(&trans->in.params, r, + &r->in.req, + (ndr_pull_flags_fn_t)ndr_pull_dfs_GetDFSReferral_in); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(2,(__location__ ": Failed to parse GetDFSReferral_in - %s\n", + nt_errstr(status))); + talloc_free(r); + return status; + } + + DEBUG(8, ("Requested DFS name: %s length: %u\n", + r->in.req.servername, + (unsigned int)strlen_m(r->in.req.servername)*2)); + + status = dfs_server_ad_get_referrals(lp_ctx, ldb, + req->smb_conn->connection->remote_address, r); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(r); + return status; + } + + ndr_err = ndr_push_struct_blob(&outblob, trans, + r->out.resp, + (ndr_push_flags_fn_t)ndr_push_dfs_referral_resp); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DEBUG(2,(__location__ ":NDR marchalling of domain deferral response failed\n")); + talloc_free(r); + return NT_STATUS_INTERNAL_ERROR; + } + + nb_referrals = r->out.resp->nb_referrals; + + if (outblob.length > trans->in.max_data) { + bool ok = false; + + DEBUG(3, ("Blob is too big for the output buffer " + "size %u max %u\n", + (unsigned int)outblob.length, trans->in.max_data)); + + if (trans->in.max_data != MAX_DFS_RESPONSE) { + /* As specified in MS-DFSC.pdf 3.3.5.2 */ + talloc_free(r); + return STATUS_BUFFER_OVERFLOW; + } + + /* + * The answer is too big, so let's remove some answers + */ + while (!ok && r->out.resp->nb_referrals > 2) { + data_blob_free(&outblob); + + /* + * Let's scrap the last referral (for now) + */ + r->out.resp->nb_referrals -= 1; + + ndr_err = ndr_push_struct_blob(&outblob, trans, + r->out.resp, + (ndr_push_flags_fn_t)ndr_push_dfs_referral_resp); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + talloc_free(r); + return NT_STATUS_INTERNAL_ERROR; + } + + if (outblob.length <= MAX_DFS_RESPONSE) { + DEBUG(10,("DFS: managed to reduce the size of referral initial" + "number of referral %d, actual count: %d", + nb_referrals, r->out.resp->nb_referrals)); + ok = true; + break; + } + } + + if (!ok && r->out.resp->nb_referrals <= 2) { + DEBUG(8, (__location__ "; Not able to fit the domain and realm in DFS a " + " 56K buffer, something must be broken")); + talloc_free(r); + return NT_STATUS_INTERNAL_ERROR; + } + } + + TRANS2_CHECK(trans2_setup_reply(trans, 0, outblob.length, 0)); + + trans->out.data = outblob; + talloc_free(r); + return NT_STATUS_OK; +} + +/* + trans2 findfirst implementation +*/ +static NTSTATUS trans2_findfirst(struct smbsrv_request *req, struct trans_op *op) +{ + struct smb_trans2 *trans = op->trans; + union smb_search_first *search; + uint16_t level; + struct find_state *state; + + /* make sure we got all the parameters */ + if (trans->in.params.length < 14) { + return NT_STATUS_FOOBAR; + } + + search = talloc(op, union smb_search_first); + NT_STATUS_HAVE_NO_MEMORY(search); + + search->t2ffirst.in.search_attrib = SVAL(trans->in.params.data, 0); + search->t2ffirst.in.max_count = SVAL(trans->in.params.data, 2); + search->t2ffirst.in.flags = SVAL(trans->in.params.data, 4); + level = SVAL(trans->in.params.data, 6); + search->t2ffirst.in.storage_type = IVAL(trans->in.params.data, 8); + + smbsrv_blob_pull_string(&req->in.bufinfo, &trans->in.params, 12, &search->t2ffirst.in.pattern, 0); + if (search->t2ffirst.in.pattern == NULL) { + return NT_STATUS_FOOBAR; + } + + search->t2ffirst.level = RAW_SEARCH_TRANS2; + search->t2ffirst.data_level = (enum smb_search_data_level)level; + if (search->t2ffirst.data_level >= RAW_SEARCH_DATA_GENERIC) { + return NT_STATUS_INVALID_LEVEL; + } + + if (search->t2ffirst.data_level == RAW_SEARCH_DATA_EA_LIST) { + TRANS2_CHECK(ea_pull_name_list(&trans->in.data, req, + &search->t2ffirst.in.num_names, + &search->t2ffirst.in.ea_names)); + } + + /* setup the private state structure that the backend will + give us in the callback */ + state = talloc(op, struct find_state); + NT_STATUS_HAVE_NO_MEMORY(state); + state->op = op; + state->search = search; + state->data_level = search->t2ffirst.data_level; + state->last_entry_offset= 0; + state->flags = search->t2ffirst.in.flags; + + /* setup for just a header in the reply */ + TRANS2_CHECK(trans2_setup_reply(trans, 10, 0, 0)); + + op->op_info = state; + op->send_fn = trans2_findfirst_send; + + return ntvfs_search_first(req->ntvfs, search, state, find_callback); +} + + +/* + trans2 findnext send +*/ +static NTSTATUS trans2_findnext_send(struct trans_op *op) +{ + struct smbsrv_request *req = op->req; + struct smb_trans2 *trans = op->trans; + union smb_search_next *search; + struct find_state *state; + uint8_t *param; + + TRANS2_CHECK_ASYNC_STATUS(state, struct find_state); + search = talloc_get_type(state->search, union smb_search_next); + + /* fill in the findfirst reply header */ + param = trans->out.params.data; + SSVAL(param, VWV(0), search->t2fnext.out.count); + SSVAL(param, VWV(1), search->t2fnext.out.end_of_search); + SSVAL(param, VWV(2), 0); + SSVAL(param, VWV(3), state->last_entry_offset); + + return NT_STATUS_OK; +} + + +/* + trans2 findnext implementation +*/ +static NTSTATUS trans2_findnext(struct smbsrv_request *req, struct trans_op *op) +{ + struct smb_trans2 *trans = op->trans; + union smb_search_next *search; + uint16_t level; + struct find_state *state; + + /* make sure we got all the parameters */ + if (trans->in.params.length < 12) { + return NT_STATUS_FOOBAR; + } + + search = talloc(op, union smb_search_next); + NT_STATUS_HAVE_NO_MEMORY(search); + + search->t2fnext.in.handle = SVAL(trans->in.params.data, 0); + search->t2fnext.in.max_count = SVAL(trans->in.params.data, 2); + level = SVAL(trans->in.params.data, 4); + search->t2fnext.in.resume_key = IVAL(trans->in.params.data, 6); + search->t2fnext.in.flags = SVAL(trans->in.params.data, 10); + + smbsrv_blob_pull_string(&req->in.bufinfo, &trans->in.params, 12, &search->t2fnext.in.last_name, 0); + if (search->t2fnext.in.last_name == NULL) { + return NT_STATUS_FOOBAR; + } + + search->t2fnext.level = RAW_SEARCH_TRANS2; + search->t2fnext.data_level = (enum smb_search_data_level)level; + if (search->t2fnext.data_level >= RAW_SEARCH_DATA_GENERIC) { + return NT_STATUS_INVALID_LEVEL; + } + + if (search->t2fnext.data_level == RAW_SEARCH_DATA_EA_LIST) { + TRANS2_CHECK(ea_pull_name_list(&trans->in.data, req, + &search->t2fnext.in.num_names, + &search->t2fnext.in.ea_names)); + } + + /* setup the private state structure that the backend will give us in the callback */ + state = talloc(op, struct find_state); + NT_STATUS_HAVE_NO_MEMORY(state); + state->op = op; + state->search = search; + state->data_level = search->t2fnext.data_level; + state->last_entry_offset= 0; + state->flags = search->t2fnext.in.flags; + + /* setup for just a header in the reply */ + TRANS2_CHECK(trans2_setup_reply(trans, 8, 0, 0)); + + op->op_info = state; + op->send_fn = trans2_findnext_send; + + return ntvfs_search_next(req->ntvfs, search, state, find_callback); +} + + +/* + backend for trans2 requests +*/ +static NTSTATUS trans2_backend(struct smbsrv_request *req, struct trans_op *op) +{ + struct smb_trans2 *trans = op->trans; + NTSTATUS status; + + /* direct trans2 pass thru */ + status = ntvfs_trans2(req->ntvfs, trans); + if (!NT_STATUS_EQUAL(NT_STATUS_NOT_IMPLEMENTED, status)) { + return status; + } + + /* must have at least one setup word */ + if (trans->in.setup_count < 1) { + return NT_STATUS_FOOBAR; + } + + /* the trans2 command is in setup[0] */ + switch (trans->in.setup[0]) { + case TRANSACT2_GET_DFS_REFERRAL: + return trans2_getdfsreferral(req, op); + case TRANSACT2_FINDFIRST: + return trans2_findfirst(req, op); + case TRANSACT2_FINDNEXT: + return trans2_findnext(req, op); + case TRANSACT2_QPATHINFO: + return trans2_qpathinfo(req, op); + case TRANSACT2_QFILEINFO: + return trans2_qfileinfo(req, op); + case TRANSACT2_SETFILEINFO: + return trans2_setfileinfo(req, op); + case TRANSACT2_SETPATHINFO: + return trans2_setpathinfo(req, op); + case TRANSACT2_QFSINFO: + return trans2_qfsinfo(req, op); + case TRANSACT2_OPEN: + return trans2_open(req, op); + case TRANSACT2_MKDIR: + return trans2_mkdir(req, op); + } + + /* an unknown trans2 command */ + return NT_STATUS_FOOBAR; +} + +int smbsrv_trans_partial_destructor(struct smbsrv_trans_partial *tp) +{ + DLIST_REMOVE(tp->req->smb_conn->trans_partial, tp); + return 0; +} + + +/* + send a continue request +*/ +static void reply_trans_continue(struct smbsrv_request *req, uint8_t command, + struct smb_trans2 *trans) +{ + struct smbsrv_request *req2; + struct smbsrv_trans_partial *tp; + int count; + + /* make sure they don't flood us */ + for (count=0,tp=req->smb_conn->trans_partial;tp;tp=tp->next) count++; + if (count > 100) { + smbsrv_send_error(req, NT_STATUS_INSUFFICIENT_RESOURCES); + return; + } + + tp = talloc(req, struct smbsrv_trans_partial); + + tp->req = req; + tp->u.trans = trans; + tp->command = command; + + DLIST_ADD(req->smb_conn->trans_partial, tp); + talloc_set_destructor(tp, smbsrv_trans_partial_destructor); + + req2 = smbsrv_setup_secondary_request(req); + + /* send a 'please continue' reply */ + smbsrv_setup_reply(req2, 0, 0); + smbsrv_send_reply(req2); +} + + +/* + answer a reconstructed trans request +*/ +static void reply_trans_send(struct ntvfs_request *ntvfs) +{ + struct smbsrv_request *req; + struct trans_op *op; + struct smb_trans2 *trans; + uint16_t params_left, data_left; + uint8_t *params, *data; + int i; + + SMBSRV_CHECK_ASYNC_STATUS_ERR(op, struct trans_op); + trans = op->trans; + + /* if this function needs work to form the nttrans reply buffer, then + call that now */ + if (op->send_fn != NULL) { + NTSTATUS status; + status = op->send_fn(op); + if (!NT_STATUS_IS_OK(status)) { + smbsrv_send_error(req, status); + return; + } + } + + params_left = trans->out.params.length; + data_left = trans->out.data.length; + params = trans->out.params.data; + data = trans->out.data.data; + + smbsrv_setup_reply(req, 10 + trans->out.setup_count, 0); + + if (!NT_STATUS_IS_OK(req->ntvfs->async_states->status)) { + smbsrv_setup_error(req, req->ntvfs->async_states->status); + } + + /* we need to divide up the reply into chunks that fit into + the negotiated buffer size */ + do { + uint16_t this_data, this_param, max_bytes; + unsigned int align1 = 1, align2 = (params_left ? 2 : 0); + struct smbsrv_request *this_req; + + max_bytes = req_max_data(req) - (align1 + align2); + + this_param = params_left; + if (this_param > max_bytes) { + this_param = max_bytes; + } + max_bytes -= this_param; + + this_data = data_left; + if (this_data > max_bytes) { + this_data = max_bytes; + } + + /* don't destroy unless this is the last chunk */ + if (params_left - this_param != 0 || + data_left - this_data != 0) { + this_req = smbsrv_setup_secondary_request(req); + } else { + this_req = req; + } + + req_grow_data(this_req, this_param + this_data + (align1 + align2)); + + SSVAL(this_req->out.vwv, VWV(0), trans->out.params.length); + SSVAL(this_req->out.vwv, VWV(1), trans->out.data.length); + SSVAL(this_req->out.vwv, VWV(2), 0); + + SSVAL(this_req->out.vwv, VWV(3), this_param); + SSVAL(this_req->out.vwv, VWV(4), align1 + PTR_DIFF(this_req->out.data, this_req->out.hdr)); + SSVAL(this_req->out.vwv, VWV(5), PTR_DIFF(params, trans->out.params.data)); + + SSVAL(this_req->out.vwv, VWV(6), this_data); + SSVAL(this_req->out.vwv, VWV(7), align1 + align2 + + PTR_DIFF(this_req->out.data + this_param, this_req->out.hdr)); + SSVAL(this_req->out.vwv, VWV(8), PTR_DIFF(data, trans->out.data.data)); + + SCVAL(this_req->out.vwv, VWV(9), trans->out.setup_count); + SCVAL(this_req->out.vwv, VWV(9)+1, 0); /* reserved */ + for (i=0;i<trans->out.setup_count;i++) { + SSVAL(this_req->out.vwv, VWV(10+i), trans->out.setup[i]); + } + + memset(this_req->out.data, 0, align1); + if (this_param != 0) { + memcpy(this_req->out.data + align1, params, this_param); + } + memset(this_req->out.data+this_param+align1, 0, align2); + if (this_data != 0) { + memcpy(this_req->out.data+this_param+align1+align2, data, this_data); + } + + params_left -= this_param; + data_left -= this_data; + params += this_param; + data += this_data; + + smbsrv_send_reply(this_req); + } while (params_left != 0 || data_left != 0); +} + + +/* + answer a reconstructed trans request +*/ +static void reply_trans_complete(struct smbsrv_request *req, uint8_t command, + struct smb_trans2 *trans) +{ + struct trans_op *op; + + SMBSRV_TALLOC_IO_PTR(op, struct trans_op); + SMBSRV_SETUP_NTVFS_REQUEST(reply_trans_send, NTVFS_ASYNC_STATE_MAY_ASYNC); + + op->req = req; + op->trans = trans; + op->command = command; + op->op_info = NULL; + op->send_fn = NULL; + + /* its a full request, give it to the backend */ + if (command == SMBtrans) { + SMBSRV_CALL_NTVFS_BACKEND(ntvfs_trans(req->ntvfs, trans)); + return; + } else { + SMBSRV_CALL_NTVFS_BACKEND(trans2_backend(req, op)); + return; + } +} + +/* + Reply to an SMBtrans or SMBtrans2 request +*/ +static void reply_trans_generic(struct smbsrv_request *req, uint8_t command) +{ + struct smb_trans2 *trans; + int i; + uint16_t param_ofs, data_ofs; + uint16_t param_count, data_count; + uint16_t param_total, data_total; + + /* parse request */ + if (req->in.wct < 14) { + smbsrv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + + trans = talloc(req, struct smb_trans2); + if (trans == NULL) { + smbsrv_send_error(req, NT_STATUS_NO_MEMORY); + return; + } + + param_total = SVAL(req->in.vwv, VWV(0)); + data_total = SVAL(req->in.vwv, VWV(1)); + trans->in.max_param = SVAL(req->in.vwv, VWV(2)); + trans->in.max_data = SVAL(req->in.vwv, VWV(3)); + trans->in.max_setup = CVAL(req->in.vwv, VWV(4)); + trans->in.flags = SVAL(req->in.vwv, VWV(5)); + trans->in.timeout = IVAL(req->in.vwv, VWV(6)); + param_count = SVAL(req->in.vwv, VWV(9)); + param_ofs = SVAL(req->in.vwv, VWV(10)); + data_count = SVAL(req->in.vwv, VWV(11)); + data_ofs = SVAL(req->in.vwv, VWV(12)); + trans->in.setup_count = CVAL(req->in.vwv, VWV(13)); + + if (req->in.wct != 14 + trans->in.setup_count) { + smbsrv_send_error(req, NT_STATUS_DOS(ERRSRV, ERRerror)); + return; + } + + /* parse out the setup words */ + trans->in.setup = talloc_array(trans, uint16_t, trans->in.setup_count); + if (trans->in.setup_count && !trans->in.setup) { + smbsrv_send_error(req, NT_STATUS_NO_MEMORY); + return; + } + for (i=0;i<trans->in.setup_count;i++) { + trans->in.setup[i] = SVAL(req->in.vwv, VWV(14+i)); + } + + if (command == SMBtrans) { + req_pull_string(&req->in.bufinfo, &trans->in.trans_name, req->in.data, -1, STR_TERMINATE); + } + + if (!req_pull_blob(&req->in.bufinfo, req->in.hdr + param_ofs, param_count, &trans->in.params) || + !req_pull_blob(&req->in.bufinfo, req->in.hdr + data_ofs, data_count, &trans->in.data)) { + smbsrv_send_error(req, NT_STATUS_FOOBAR); + return; + } + + /* is it a partial request? if so, then send a 'send more' message */ + if (param_total > param_count || data_total > data_count) { + reply_trans_continue(req, command, trans); + return; + } + + reply_trans_complete(req, command, trans); +} + + +/* + Reply to an SMBtranss2 request +*/ +static void reply_transs_generic(struct smbsrv_request *req, uint8_t command) +{ + struct smbsrv_trans_partial *tp; + struct smb_trans2 *trans = NULL; + uint16_t param_ofs, data_ofs; + uint16_t param_count, data_count; + uint16_t param_disp, data_disp; + uint16_t param_total, data_total; + DATA_BLOB params, data; + uint8_t wct; + + if (command == SMBtrans2) { + wct = 9; + } else { + wct = 8; + } + + /* parse request */ + if (req->in.wct != wct) { + /* + * TODO: add some error code tests + * w2k3 returns NT_STATUS_DOS(ERRSRV, ERRerror) here + */ + smbsrv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + + for (tp=req->smb_conn->trans_partial;tp;tp=tp->next) { + if (tp->command == command && + SVAL(tp->req->in.hdr, HDR_MID) == SVAL(req->in.hdr, HDR_MID)) { +/* TODO: check the VUID, PID and TID too? */ + break; + } + } + + if (tp == NULL) { + smbsrv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + + trans = tp->u.trans; + + param_total = SVAL(req->in.vwv, VWV(0)); + data_total = SVAL(req->in.vwv, VWV(1)); + param_count = SVAL(req->in.vwv, VWV(2)); + param_ofs = SVAL(req->in.vwv, VWV(3)); + param_disp = SVAL(req->in.vwv, VWV(4)); + data_count = SVAL(req->in.vwv, VWV(5)); + data_ofs = SVAL(req->in.vwv, VWV(6)); + data_disp = SVAL(req->in.vwv, VWV(7)); + + if (!req_pull_blob(&req->in.bufinfo, req->in.hdr + param_ofs, param_count, ¶ms) || + !req_pull_blob(&req->in.bufinfo, req->in.hdr + data_ofs, data_count, &data)) { + smbsrv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + + /* only allow contiguous requests */ + if ((param_count != 0 && + param_disp != trans->in.params.length) || + (data_count != 0 && + data_disp != trans->in.data.length)) { + smbsrv_send_error(req, NT_STATUS_INVALID_PARAMETER); + return; + } + + /* add to the existing request */ + if (param_count != 0) { + trans->in.params.data = talloc_realloc(trans, + trans->in.params.data, + uint8_t, + param_disp + param_count); + if (trans->in.params.data == NULL) { + smbsrv_send_error(tp->req, NT_STATUS_NO_MEMORY); + return; + } + trans->in.params.length = param_disp + param_count; + } + + if (data_count != 0) { + trans->in.data.data = talloc_realloc(trans, + trans->in.data.data, + uint8_t, + data_disp + data_count); + if (trans->in.data.data == NULL) { + smbsrv_send_error(tp->req, NT_STATUS_NO_MEMORY); + return; + } + trans->in.data.length = data_disp + data_count; + } + + memcpy(trans->in.params.data + param_disp, params.data, params.length); + memcpy(trans->in.data.data + data_disp, data.data, data.length); + + /* the sequence number of the reply is taken from the last secondary + response */ + tp->req->seq_num = req->seq_num; + + /* we don't reply to Transs2 requests */ + talloc_free(req); + + if (trans->in.params.length == param_total && + trans->in.data.length == data_total) { + /* its now complete */ + req = tp->req; + talloc_free(tp); + reply_trans_complete(req, command, trans); + } + return; +} + + +/* + Reply to an SMBtrans2 +*/ +void smbsrv_reply_trans2(struct smbsrv_request *req) +{ + reply_trans_generic(req, SMBtrans2); +} + +/* + Reply to an SMBtrans +*/ +void smbsrv_reply_trans(struct smbsrv_request *req) +{ + reply_trans_generic(req, SMBtrans); +} + +/* + Reply to an SMBtranss request +*/ +void smbsrv_reply_transs(struct smbsrv_request *req) +{ + reply_transs_generic(req, SMBtrans); +} + +/* + Reply to an SMBtranss2 request +*/ +void smbsrv_reply_transs2(struct smbsrv_request *req) +{ + reply_transs_generic(req, SMBtrans2); +} diff --git a/source4/smb_server/smb/wscript_build b/source4/smb_server/smb/wscript_build new file mode 100644 index 0000000..3e3df21 --- /dev/null +++ b/source4/smb_server/smb/wscript_build @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +bld.SAMBA_SUBSYSTEM('SMB_PROTOCOL', + source='receive.c negprot.c nttrans.c reply.c request.c search.c service.c sesssetup.c srvtime.c trans2.c signing.c', + autoproto='smb_proto.h', + deps='dfs_server_ad', + public_deps='ntvfs LIBPACKET samba-credentials samba_server_gensec', + enabled=bld.CONFIG_SET('WITH_NTVFS_FILESERVER') + ) + 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') + ) + diff --git a/source4/smb_server/smb_server.c b/source4/smb_server/smb_server.c new file mode 100644 index 0000000..c45df70 --- /dev/null +++ b/source4/smb_server/smb_server.c @@ -0,0 +1,203 @@ +/* + Unix SMB/CIFS implementation. + process incoming packets - main loop + Copyright (C) Andrew Tridgell 2004-2005 + Copyright (C) Stefan Metzmacher 2004-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 "samba/service_task.h" +#include "samba/service_stream.h" +#include "samba/service.h" +#include "smb_server/smb_server.h" +#include "smb_server/service_smb_proto.h" +#include "lib/messaging/irpc.h" +#include "lib/stream/packet.h" +#include "libcli/smb2/smb2.h" +#include "smb_server/smb2/smb2_server.h" +#include "system/network.h" +#include "lib/socket/netif.h" +#include "param/share.h" +#include "dsdb/samdb/samdb.h" +#include "param/param.h" + +static NTSTATUS smbsrv_recv_generic_request(void *private_data, DATA_BLOB blob) +{ + NTSTATUS status; + struct smbsrv_connection *smb_conn = talloc_get_type(private_data, struct smbsrv_connection); + uint32_t protocol_version; + + /* see if its a special NBT packet */ + if (CVAL(blob.data,0) != 0) { + status = smbsrv_init_smb_connection(smb_conn, smb_conn->lp_ctx); + NT_STATUS_NOT_OK_RETURN(status); + return smbsrv_recv_smb_request(smb_conn, blob); + } + + if (blob.length < (NBT_HDR_SIZE + MIN_SMB_SIZE)) { + DEBUG(2,("Invalid SMB packet length count %ld\n", (long)blob.length)); + smbsrv_terminate_connection(smb_conn, "Invalid SMB packet"); + return NT_STATUS_OK; + } + + protocol_version = IVAL(blob.data, NBT_HDR_SIZE); + + switch (protocol_version) { + case SMB_MAGIC: + status = smbsrv_init_smb_connection(smb_conn, smb_conn->lp_ctx); + NT_STATUS_NOT_OK_RETURN(status); + packet_set_callback(smb_conn->packet, smbsrv_recv_smb_request); + return smbsrv_recv_smb_request(smb_conn, blob); + case SMB2_MAGIC: + if (lpcfg_server_max_protocol(smb_conn->lp_ctx) < PROTOCOL_SMB2_02) break; + status = smbsrv_init_smb2_connection(smb_conn); + NT_STATUS_NOT_OK_RETURN(status); + packet_set_callback(smb_conn->packet, smbsrv_recv_smb2_request); + return smbsrv_recv_smb2_request(smb_conn, blob); + } + + DEBUG(2,("Invalid SMB packet: protocol prefix: 0x%08X\n", protocol_version)); + smbsrv_terminate_connection(smb_conn, "NON-SMB packet"); + return NT_STATUS_OK; +} + +/* + close the socket and shutdown a server_context +*/ +void smbsrv_terminate_connection(struct smbsrv_connection *smb_conn, const char *reason) +{ + stream_terminate_connection(smb_conn->connection, reason); +} + +/* + called when a SMB socket becomes readable +*/ +static void smbsrv_recv(struct stream_connection *conn, uint16_t flags) +{ + struct smbsrv_connection *smb_conn = talloc_get_type(conn->private_data, + struct smbsrv_connection); + + DEBUG(10,("smbsrv_recv\n")); + + packet_recv(smb_conn->packet); +} + +/* + called when a SMB socket becomes writable +*/ +static void smbsrv_send(struct stream_connection *conn, uint16_t flags) +{ + struct smbsrv_connection *smb_conn = talloc_get_type(conn->private_data, + struct smbsrv_connection); + packet_queue_run(smb_conn->packet); +} + +/* + handle socket recv errors +*/ +static void smbsrv_recv_error(void *private_data, NTSTATUS status) +{ + struct smbsrv_connection *smb_conn = talloc_get_type(private_data, struct smbsrv_connection); + + smbsrv_terminate_connection(smb_conn, nt_errstr(status)); +} + +/* + initialise a server_context from a open socket and register a event handler + for reading from that socket +*/ +static void smbsrv_accept(struct stream_connection *conn) +{ + struct smbsrv_connection *smb_conn; + + DEBUG(5,("smbsrv_accept\n")); + + smb_conn = talloc_zero(conn, struct smbsrv_connection); + if (!smb_conn) { + stream_terminate_connection(conn, "out of memory"); + return; + } + + smb_conn->packet = packet_init(smb_conn); + if (!smb_conn->packet) { + smbsrv_terminate_connection(smb_conn, "out of memory"); + return; + } + packet_set_private(smb_conn->packet, smb_conn); + packet_set_socket(smb_conn->packet, conn->socket); + packet_set_callback(smb_conn->packet, smbsrv_recv_generic_request); + packet_set_full_request(smb_conn->packet, packet_full_request_nbt); + packet_set_error_handler(smb_conn->packet, smbsrv_recv_error); + packet_set_event_context(smb_conn->packet, conn->event.ctx); + packet_set_fde(smb_conn->packet, conn->event.fde); + packet_set_serialise(smb_conn->packet); + packet_set_initial_read(smb_conn->packet, 4); + + smb_conn->lp_ctx = conn->lp_ctx; + smb_conn->connection = conn; + conn->private_data = smb_conn; + + smb_conn->statistics.connect_time = timeval_current(); + + smbsrv_management_init(smb_conn); + + irpc_add_name(conn->msg_ctx, "smb_server"); + + if (!NT_STATUS_IS_OK(share_get_context(smb_conn, + smb_conn->lp_ctx, + &(smb_conn->share_context)))) { + smbsrv_terminate_connection(smb_conn, "share_init failed!"); + return; + } +} + +static const struct stream_server_ops smb_stream_ops = { + .name = "smbsrv", + .accept_connection = smbsrv_accept, + .recv_handler = smbsrv_recv, + .send_handler = smbsrv_send, +}; + +/* + setup a listening socket on all the SMB ports for a particular address +*/ +_PUBLIC_ NTSTATUS smbsrv_add_socket(TALLOC_CTX *mem_ctx, + struct tevent_context *event_context, + struct loadparm_context *lp_ctx, + const struct model_ops *model_ops, + const char *address, + void *process_context) +{ + const char **ports = lpcfg_smb_ports(lp_ctx); + int i; + NTSTATUS status; + + for (i=0;ports[i];i++) { + uint16_t port = atoi(ports[i]); + if (port == 0) continue; + status = stream_setup_socket(mem_ctx, event_context, lp_ctx, + model_ops, &smb_stream_ops, + "ip", address, &port, + lpcfg_socket_options(lp_ctx), + NULL, process_context); + NT_STATUS_NOT_OK_RETURN(status); + } + + return NT_STATUS_OK; +} + + + diff --git a/source4/smb_server/smb_server.h b/source4/smb_server/smb_server.h new file mode 100644 index 0000000..5ddfe78 --- /dev/null +++ b/source4/smb_server/smb_server.h @@ -0,0 +1,522 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) James J Myers 2003 <myersjj@samba.org> + Copyright (C) Stefan Metzmacher 2004-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 "libcli/raw/request.h" +#include "libcli/raw/interfaces.h" +#include "lib/socket/socket.h" +#include "libds/common/roles.h" +#include "../lib/util/dlinklist.h" +#include "../librpc/gen_ndr/nbt.h" + +struct tevent_context; + +/* + this header declares the core context structures associated with smb + sockets, tree connects, requests etc + + the idea is that we will eventually get rid of all our global + variables and instead store our state from structures hanging off + these basic elements +*/ + +struct smbsrv_tcons_context { + /* an id tree used to allocate tids */ + struct idr_context *idtree_tid; + + /* this is the limit of vuid values for this connection */ + uint32_t idtree_limit; + + /* list of open tree connects */ + struct smbsrv_tcon *list; +}; + +struct smbsrv_sessions_context { + /* an id tree used to allocate vuids */ + /* this holds info on session vuids that are already + * validated for this VC */ + struct idr_context *idtree_vuid; + + /* this is the limit of vuid values for this connection */ + uint64_t idtree_limit; + + /* also kept as a link list so it can be enumerated by + the management code */ + struct smbsrv_session *list; +}; + +struct smbsrv_handles_context { + /* an id tree used to allocate file handles */ + struct idr_context *idtree_hid; + + /* this is the limit of handle values for this context */ + uint64_t idtree_limit; + + /* also kept as a link list so it can be enumerated by + the management code */ + struct smbsrv_handle *list; +}; + +/* the current user context for a request */ +struct smbsrv_session { + struct smbsrv_session *prev, *next; + + struct smbsrv_connection *smb_conn; + + /* + * in SMB2 tcons belong to just one session + * and not to the whole connection + */ + struct smbsrv_tcons_context smb2_tcons; + + /* + * the open file handles for this session, + * used for SMBexit, SMBulogoff and SMB2 SessionLogoff + */ + struct smbsrv_handle_session_item *handles; + + /* + * an index passed over the wire: + * - 16 bit for smb + * - 64 bit for smb2 + */ + uint64_t vuid; + + struct gensec_security *gensec_ctx; + + struct auth_session_info *session_info; + + struct { + bool required; + bool active; + } smb2_signing; + + /* some statistics for the management tools */ + struct { + /* the time when the session setup started */ + struct timeval connect_time; + /* the time when the session setup was finished */ + struct timeval auth_time; + /* the time when the last request comes in */ + struct timeval last_request_time; + } statistics; +}; + +/* we need a forward declaration of the ntvfs_ops strucutre to prevent + include recursion */ +struct ntvfs_context; + +struct smbsrv_tcon { + struct smbsrv_tcon *next, *prev; + + /* the server context that this was created on */ + struct smbsrv_connection *smb_conn; + + /* the open file handles on this tcon */ + struct smbsrv_handles_context handles; + + /* + * an index passed over the wire: + * - 16 bit for smb + * - 32 bit for smb2 + */ + uint32_t tid; /* an index passed over the wire (the TID) */ + + /* the share name */ + const char *share_name; + + /* the NTVFS context - see source/ntvfs/ for details */ + struct ntvfs_context *ntvfs; + + /* some stuff to support share level security */ + struct { + /* in share level security we need to fake up a session */ + struct smbsrv_session *session; + } sec_share; + + /* some stuff to support share level security */ + struct { + /* in SMB2 a tcon always belongs to one session */ + struct smbsrv_session *session; + } smb2; + + /* some statistics for the management tools */ + struct { + /* the time when the tree connect started */ + struct timeval connect_time; + /* the time when the last request comes in */ + struct timeval last_request_time; + } statistics; +}; + +struct smbsrv_handle { + struct smbsrv_handle *next, *prev; + + /* the tcon the handle belongs to */ + struct smbsrv_tcon *tcon; + + /* the session the handle was opened on */ + struct smbsrv_session *session; + + /* the smbpid used on the open, used for SMBexit */ + uint16_t smbpid; + + /* + * this is for adding the handle into a linked list + * on the smbsrv_session, we can't use *next,*prev + * for this because they're used for the linked list on the + * smbsrv_tcon + */ + struct smbsrv_handle_session_item { + struct smbsrv_handle_session_item *prev, *next; + struct smbsrv_handle *handle; + } session_item; + + /* + * the value passed over the wire + * - 16 bit for smb + * - 32 bit for smb2 + * Note: for SMB2 handles are 128 bit + * we'll fill them with + * - 32 bit HID + * - 32 bit TID + * - 64 bit VUID + */ + uint32_t hid; + + /* + * the ntvfs handle passed to the ntvfs backend + */ + struct ntvfs_handle *ntvfs; + + /* some statistics for the management tools */ + struct { + /* the time when the tree connect started */ + struct timeval open_time; + /* the time when the last request comes in */ + struct timeval last_use_time; + } statistics; +}; + +/* a set of flags to control handling of request structures */ +#define SMBSRV_REQ_CONTROL_LARGE (1<<1) /* allow replies larger than max_xmit */ + +#define SMBSRV_REQ_DEFAULT_STR_FLAGS(req) (((req)->flags2 & FLAGS2_UNICODE_STRINGS) ? STR_UNICODE : STR_ASCII) + +/* the context for a single SMB request. This is passed to any request-context + functions */ +struct smbsrv_request { + /* the smbsrv_connection needs a list of requests queued for send */ + struct smbsrv_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; + + /* a set of flags to control usage of the request. See SMBSRV_REQ_CONTROL_* */ + 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 SMB specific stuff */ + + /* the flags from the SMB request, in raw form (host byte order) */ + uint16_t flags2; + + /* this can contain a fnum from an earlier part of a chained + * message (such as an SMBOpenX), or -1 */ + int chained_fnum; + + /* how far through the chain of SMB commands have we gone? */ + unsigned chain_count; + + /* the sequence number for signing */ + uint64_t seq_num; + + struct smb_request_buffer in; + struct smb_request_buffer out; +}; + +/* smb server context structure. This should contain all the state + * information associated with a SMB server connection + */ +struct smbsrv_connection { + /* context that has been negotiated between the client and server */ + struct { + /* have we already done the NBT session establishment? */ + bool done_nbt_session; + + /* only one negprot per connection is allowed */ + bool done_negprot; + + /* multiple session setups are allowed, but some parameters are + ignored in any but the first */ + bool done_sesssetup; + + /* + * Size of data we can send to client. Set + * by the client for all protocols above CORE. + * Set by us for CORE protocol. + */ + unsigned max_send; /* init to BUFFER_SIZE */ + + /* + * Size of the data we can receive. Set by us. + * Can be modified by the max xmit parameter. + */ + unsigned max_recv; /* init to BUFFER_SIZE */ + + /* the negotiatiated protocol */ + enum protocol_types protocol; + + /* authentication context for multi-part negprot */ + struct auth4_context *auth_context; + + /* reference to the kerberos keytab, or machine trust account */ + struct cli_credentials *server_credentials; + + /* did we tell the client we support encrypted passwords? */ + bool encrypted_passwords; + + /* Did we choose SPNEGO, or perhaps raw NTLMSSP, or even no extended security at all? */ + const char *oid; + + /* client capabilities */ + uint32_t client_caps; + + /* the timezone we sent to the client */ + int zone_offset; + + /* NBT names only set when done_nbt_session is true */ + struct nbt_name *called_name; + struct nbt_name *calling_name; + } negotiate; + + /* the context associated with open tree connects on a smb socket, not for SMB2 */ + struct smbsrv_tcons_context smb_tcons; + + /* context associated with currently valid session setups */ + struct smbsrv_sessions_context sessions; + + /* + * the server_context holds a linked list of pending requests, + * this is used for finding the request structures on ntcancel requests + * For SMB only + */ + struct smbsrv_request *requests; + + /* + * the server_context holds a linked list of pending requests, + * and an idtree for finding the request structures on SMB2 Cancel + * For SMB2 only + */ + struct { + /* an id tree used to allocate ids */ + struct idr_context *idtree_req; + + /* this is the limit of pending requests values for this connection */ + uint32_t idtree_limit; + + /* list of open tree connects */ + struct smb2srv_request *list; + } requests2; + + struct smb_signing_context signing; + + struct stream_connection *connection; + + /* this holds a partially received request */ + struct packet_context *packet; + + /* a list of partially received transaction requests */ + struct smbsrv_trans_partial { + struct smbsrv_trans_partial *next, *prev; + struct smbsrv_request *req; + uint8_t command; + union { + struct smb_trans2 *trans; + struct smb_nttrans *nttrans; + } u; + } *trans_partial; + + /* configuration parameters */ + struct { + bool nt_status_support; + } config; + + /* some statictics for the management tools */ + struct { + /* the time when the client connects */ + struct timeval connect_time; + /* the time when the last request comes in */ + struct timeval last_request_time; + } statistics; + + struct share_context *share_context; + + struct loadparm_context *lp_ctx; + + bool smb2_signing_required; + + uint64_t highest_smb2_seqnum; +}; + +struct model_ops; +struct loadparm_context; + +NTSTATUS smbsrv_add_socket(TALLOC_CTX *mem_ctx, + struct tevent_context *event_context, + struct loadparm_context *lp_ctx, + const struct model_ops *model_ops, + const char *address, + void *process_context); + +struct loadparm_context; + +#include "smb_server/smb_server_proto.h" +#include "smb_server/smb/smb_proto.h" + +/* useful way of catching wct errors with file and line number */ +#define SMBSRV_CHECK_WCT(req, wcount) do { \ + if ((req)->in.wct != (wcount)) { \ + DEBUG(1,("Unexpected WCT %u at %s(%d) - expected %d\n", \ + (req)->in.wct, __FILE__, __LINE__, wcount)); \ + smbsrv_send_error(req, NT_STATUS_DOS(ERRSRV, ERRerror)); \ + return; \ + } \ +} while (0) + +/* useful wrapper for talloc with NO_MEMORY reply */ +#define SMBSRV_TALLOC_IO_PTR(ptr, type) do { \ + ptr = talloc(req, type); \ + if (!ptr) { \ + smbsrv_send_error(req, NT_STATUS_NO_MEMORY); \ + return; \ + } \ + req->io_ptr = ptr; \ +} while (0) + +#define SMBSRV_SETUP_NTVFS_REQUEST(send_fn, state) do { \ + req->ntvfs = ntvfs_request_create(req->tcon->ntvfs, req, \ + req->session->session_info,\ + SVAL(req->in.hdr,HDR_PID), \ + req->request_time, \ + req, send_fn, state); \ + if (!req->ntvfs) { \ + smbsrv_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 SMBSRV_CHECK_FILE_HANDLE(handle) do { \ + if (!handle) { \ + smbsrv_send_error(req, NT_STATUS_INVALID_HANDLE); \ + return; \ + } \ +} while (0) + +#define SMBSRV_CHECK_FILE_HANDLE_ERROR(handle, _status) do { \ + if (!handle) { \ + smbsrv_send_error(req, _status); \ + return; \ + } \ +} while (0) + +#define SMBSRV_CHECK_FILE_HANDLE_NTSTATUS(handle) do { \ + if (!handle) { \ + return NT_STATUS_INVALID_HANDLE; \ + } \ +} while (0) + +#define SMBSRV_CHECK(cmd) do {\ + NTSTATUS _status; \ + _status = cmd; \ + if (!NT_STATUS_IS_OK(_status)) { \ + smbsrv_send_error(req, _status); \ + 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 SMBSRV_CALL_NTVFS_BACKEND(cmd) do { \ + req->ntvfs->async_states->status = cmd; \ + if (req->ntvfs->async_states->state & NTVFS_ASYNC_STATE_ASYNC) { \ + DLIST_ADD_END(req->smb_conn->requests, req); \ + } 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 SMBSRV_CHECK_ASYNC_STATUS_ERR_SIMPLE do { \ + req = talloc_get_type(ntvfs->async_states->private_data, struct smbsrv_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; \ + } \ + if (NT_STATUS_IS_ERR(ntvfs->async_states->status)) { \ + smbsrv_send_error(req, ntvfs->async_states->status); \ + return; \ + } \ +} while (0) +#define SMBSRV_CHECK_ASYNC_STATUS_ERR(ptr, type) do { \ + SMBSRV_CHECK_ASYNC_STATUS_ERR_SIMPLE; \ + ptr = talloc_get_type(req->io_ptr, type); \ +} while (0) +#define SMBSRV_CHECK_ASYNC_STATUS_SIMPLE do { \ + req = talloc_get_type(ntvfs->async_states->private_data, struct smbsrv_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; \ + } \ + if (!NT_STATUS_IS_OK(ntvfs->async_states->status)) { \ + smbsrv_send_error(req, ntvfs->async_states->status); \ + return; \ + } \ +} while (0) +#define SMBSRV_CHECK_ASYNC_STATUS(ptr, type) do { \ + SMBSRV_CHECK_ASYNC_STATUS_SIMPLE; \ + ptr = talloc_get_type(req->io_ptr, type); \ +} while (0) + +/* zero out some reserved fields in a reply */ +#define SMBSRV_VWV_RESERVED(start, count) memset(req->out.vwv + VWV(start), 0, (count)*2) + +#include "smb_server/service_smb_proto.h" diff --git a/source4/smb_server/tcon.c b/source4/smb_server/tcon.c new file mode 100644 index 0000000..39bc0a5 --- /dev/null +++ b/source4/smb_server/tcon.c @@ -0,0 +1,194 @@ +/* + Unix SMB/CIFS implementation. + Manage smbsrv_tcon structures + Copyright (C) Andrew Tridgell 1998 + Copyright (C) Alexander Bokovoy 2002 + Copyright (C) Stefan Metzmacher 2005-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 "smb_server/smb_server.h" +#include "samba/service_stream.h" +#include "lib/tsocket/tsocket.h" +#include "ntvfs/ntvfs.h" + +/**************************************************************************** +init the tcon structures +****************************************************************************/ +static NTSTATUS smbsrv_init_tcons(struct smbsrv_tcons_context *tcons_ctx, TALLOC_CTX *mem_ctx, uint32_t limit) +{ + /* + * the idr_* functions take 'int' as limit, + * and only work with a max limit 0x00FFFFFF + */ + limit &= 0x00FFFFFF; + + tcons_ctx->idtree_tid = idr_init(mem_ctx); + NT_STATUS_HAVE_NO_MEMORY(tcons_ctx->idtree_tid); + tcons_ctx->idtree_limit = limit; + tcons_ctx->list = NULL; + + return NT_STATUS_OK; +} + +NTSTATUS smbsrv_smb_init_tcons(struct smbsrv_connection *smb_conn) +{ + return smbsrv_init_tcons(&smb_conn->smb_tcons, smb_conn, UINT16_MAX); +} + +NTSTATUS smbsrv_smb2_init_tcons(struct smbsrv_session *smb_sess) +{ + return smbsrv_init_tcons(&smb_sess->smb2_tcons, smb_sess, UINT32_MAX); +} + +/**************************************************************************** +find a tcon given a tid for SMB +****************************************************************************/ +static struct smbsrv_tcon *smbsrv_tcon_find(struct smbsrv_tcons_context *tcons_ctx, + uint32_t tid, struct timeval request_time) +{ + void *p; + struct smbsrv_tcon *tcon; + + if (tid == 0) return NULL; + + if (tid > tcons_ctx->idtree_limit) return NULL; + + p = idr_find(tcons_ctx->idtree_tid, tid); + if (!p) return NULL; + + tcon = talloc_get_type(p, struct smbsrv_tcon); + if (!tcon) return NULL; + + tcon->statistics.last_request_time = request_time; + + return tcon; +} + +struct smbsrv_tcon *smbsrv_smb_tcon_find(struct smbsrv_connection *smb_conn, + uint32_t tid, struct timeval request_time) +{ + return smbsrv_tcon_find(&smb_conn->smb_tcons, tid, request_time); +} + +struct smbsrv_tcon *smbsrv_smb2_tcon_find(struct smbsrv_session *smb_sess, + uint32_t tid, struct timeval request_time) +{ + if (!smb_sess) return NULL; + return smbsrv_tcon_find(&smb_sess->smb2_tcons, tid, request_time); +} + +/* + destroy a connection structure +*/ +static int smbsrv_tcon_destructor(struct smbsrv_tcon *tcon) +{ + struct smbsrv_tcons_context *tcons_ctx; + struct tsocket_address *client_addr; + + client_addr = tcon->smb_conn->connection->remote_address; + + DEBUG(3,("%s closed connection to service %s\n", + tsocket_address_string(client_addr, tcon), + tcon->share_name)); + + /* tell the ntvfs backend that we are disconnecting */ + if (tcon->ntvfs) { + ntvfs_disconnect(tcon->ntvfs); + tcon->ntvfs = NULL; + } + + if (tcon->smb2.session) { + tcons_ctx = &tcon->smb2.session->smb2_tcons; + } else { + tcons_ctx = &tcon->smb_conn->smb_tcons; + } + + idr_remove(tcons_ctx->idtree_tid, tcon->tid); + DLIST_REMOVE(tcons_ctx->list, tcon); + return 0; +} + +/* + find first available connection slot +*/ +static struct smbsrv_tcon *smbsrv_tcon_new(struct smbsrv_connection *smb_conn, + struct smbsrv_session *smb_sess, + const char *share_name) +{ + TALLOC_CTX *mem_ctx; + struct smbsrv_tcons_context *tcons_ctx; + uint32_t handle_uint_max; + struct smbsrv_tcon *tcon; + NTSTATUS status; + int i; + + if (smb_sess) { + mem_ctx = smb_sess; + tcons_ctx = &smb_sess->smb2_tcons; + handle_uint_max = UINT32_MAX; + } else { + mem_ctx = smb_conn; + tcons_ctx = &smb_conn->smb_tcons; + handle_uint_max = UINT16_MAX; + } + + tcon = talloc_zero(mem_ctx, struct smbsrv_tcon); + if (!tcon) return NULL; + tcon->smb_conn = smb_conn; + tcon->smb2.session = smb_sess; + tcon->share_name = talloc_strdup(tcon, share_name); + if (!tcon->share_name) goto failed; + + /* + * the use -1 here, because we don't want to give away the wildcard + * fnum used in SMBflush + */ + status = smbsrv_init_handles(tcon, handle_uint_max - 1); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1,("ERROR! failed to init handles: %s\n", nt_errstr(status))); + goto failed; + } + + i = idr_get_new_random(tcons_ctx->idtree_tid, tcon, tcons_ctx->idtree_limit); + if (i == -1) { + DEBUG(1,("ERROR! Out of connection structures\n")); + goto failed; + } + tcon->tid = i; + + DLIST_ADD(tcons_ctx->list, tcon); + talloc_set_destructor(tcon, smbsrv_tcon_destructor); + + /* now fill in some statistics */ + tcon->statistics.connect_time = timeval_current(); + + return tcon; + +failed: + talloc_free(tcon); + return NULL; +} + +struct smbsrv_tcon *smbsrv_smb_tcon_new(struct smbsrv_connection *smb_conn, const char *share_name) +{ + return smbsrv_tcon_new(smb_conn, NULL, share_name); +} + +struct smbsrv_tcon *smbsrv_smb2_tcon_new(struct smbsrv_session *smb_sess, const char *share_name) +{ + return smbsrv_tcon_new(smb_sess->smb_conn, smb_sess, share_name); +} diff --git a/source4/smb_server/wscript_build b/source4/smb_server/wscript_build new file mode 100644 index 0000000..0ae499e --- /dev/null +++ b/source4/smb_server/wscript_build @@ -0,0 +1,21 @@ +#!/usr/bin/env python + +bld.SAMBA_MODULE('service_smb', + source='service_smb.c', + autoproto='service_smb_proto.h', + subsystem='service', + init_function='server_service_smb_init', + deps='SMB_SERVER netif shares samba-hostconfig cmdline', + internal_module=False, + enabled=bld.CONFIG_SET('WITH_NTVFS_FILESERVER') + ) + +bld.SAMBA_SUBSYSTEM('SMB_SERVER', + source='handle.c tcon.c session.c blob.c management.c smb_server.c', + autoproto='smb_server_proto.h', + public_deps='share LIBPACKET SMB_PROTOCOL SMB2_PROTOCOL', + enabled=bld.CONFIG_SET('WITH_NTVFS_FILESERVER') + ) + +bld.RECURSE('smb') +bld.RECURSE('smb2') |