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 2 of the License, or
+ 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,
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, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ 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/service_smb_proto.h"
#include "smb_server/smb2/smb2_server.h"
#include "smbd/service_stream.h"
#include "lib/stream/packet.h"
+#include "ntvfs/ntvfs.h"
+#include "param/param.h"
+#include "auth/auth.h"
-static struct smb2srv_request *smb2srv_init_request(struct smbsrv_connection *smb_conn)
+/* 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->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)
+ 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;
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_size(req, req->out.allocated);
+ 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_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_PAD1, 0);
- 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_UNKNOWN1,0x0001);
- SIVAL(req->out.hdr, SMB2_HDR_FLAGS, 0x00000001);
- SIVAL(req->out.hdr, SMB2_HDR_UNKNOWN2,0);
- SBVAL(req->out.hdr, SMB2_HDR_SEQNUM, req->seqnum);
- SIVAL(req->out.hdr, SMB2_HDR_PID, IVAL(req->in.hdr, SMB2_HDR_PID));
- SIVAL(req->out.hdr, SMB2_HDR_TID, IVAL(req->in.hdr, SMB2_HDR_TID));
- SBVAL(req->out.hdr, SMB2_HDR_UID, BVAL(req->in.hdr, SMB2_HDR_UID));
- memset(req->out.hdr+SMB2_HDR_SIG, 0, 16);
+ 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));
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;
}
if (req->out.size > NBT_HDR_SIZE) {
- _smb2_setlen(req->out.buffer, 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);
}
return;
}
- status = smb2srv_setup_reply(req, 8, True, 0);
+ 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);
SSVAL(req->out.body, 0x02, 0);
SIVAL(req->out.body, 0x04, 0);
+ req->chain_status = NT_STATUS_INVALID_PARAMETER;
+
smb2srv_send_reply(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;
+ }
- opcode = SVAL(req->in.hdr, SMB2_HDR_OPCODE);
- req->seqnum = BVAL(req->in.hdr, SMB2_HDR_SEQNUM);
- tid = IVAL(req->in.hdr, SMB2_HDR_TID);
- uid = BVAL(req->in.hdr, SMB2_HDR_UID);
+ 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;
- /* TODO: check the seqnum */
+ /* 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_ioctl_recv(req);
return NT_STATUS_OK;
case SMB2_OP_CANCEL:
- if (!req->session) goto nosession;
- if (!req->tcon) goto notcon;
smb2srv_cancel_recv(req);
return NT_STATUS_OK;
case SMB2_OP_KEEPALIVE:
smb2srv_keepalive_recv(req);
return NT_STATUS_OK;
- case SMB2_OP_FIND:
+ case SMB2_OP_QUERY_DIRECTORY:
if (!req->session) goto nosession;
if (!req->tcon) goto notcon;
smb2srv_find_recv(req);
return NT_STATUS_OK;
}
-NTSTATUS smbsrv_recv_smb2_request(void *private, DATA_BLOB blob)
+NTSTATUS smbsrv_recv_smb2_request(void *private_data, DATA_BLOB blob)
{
- struct smbsrv_connection *smb_conn = talloc_get_type(private, struct smbsrv_connection);
+ 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;
return NT_STATUS_OK;
}
- if (blob.length < (NBT_HDR_SIZE + SMB2_MIN_SIZE)) {
+ 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: protocl prefix: 0x%08X\n", protocol_version));
+ 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->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;
}
}
+ 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(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
*/
/* this is the size that w2k uses, and it appears to be important for
good performance */
- smb_conn->negotiate.max_recv = lp_max_xmit();
+ 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.security = SEC_USER;
- smb_conn->config.nt_status_support = True;
+ 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;
}