/*
   Unix SMB/CIFS implementation.
   Core SMB2 server

   Copyright (C) Stefan Metzmacher 2009
   Copyright (C) Jeremy Allison 2010

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "includes.h"
#include "system/network.h"
#include "smbd/smbd.h"
#include "smbd/globals.h"
#include "source3/smbd/smbXsrv_session.h"
#include "smbd/smbXsrv_open.h"
#include "lib/param/param.h"
#include "../libcli/smb/smb_common.h"
#include "../lib/tsocket/tsocket.h"
#include "../lib/util/tevent_ntstatus.h"
#include "smbprofile.h"
#include "../lib/util/bitmap.h"
#include "../librpc/gen_ndr/krb5pac.h"
#include "lib/util/iov_buf.h"
#include "lib/async_req/async_sock.h"
#include "auth.h"
#include "libcli/smb/smbXcli_base.h"
#include "source3/lib/substitute.h"

#if defined(LINUX)
/* SIOCOUTQ TIOCOUTQ are the same */
#define __IOCTL_SEND_QUEUE_SIZE_OPCODE TIOCOUTQ
#define __HAVE_TCP_INFO_RTO 1
#define __ALLOW_MULTI_CHANNEL_SUPPORT 1
#elif defined(FREEBSD)
#define __IOCTL_SEND_QUEUE_SIZE_OPCODE FIONWRITE
#define __HAVE_TCP_INFO_RTO 1
#define __ALLOW_MULTI_CHANNEL_SUPPORT 1
#endif

#include "lib/crypto/gnutls_helpers.h"
#include <gnutls/gnutls.h>
#include <gnutls/crypto.h>

#undef DBGC_CLASS
#define DBGC_CLASS DBGC_SMB2

static void smbd_smb2_connection_handler(struct tevent_context *ev,
					 struct tevent_fd *fde,
					 uint16_t flags,
					 void *private_data);
static NTSTATUS smbd_smb2_flush_send_queue(struct smbXsrv_connection *xconn);

static const struct smbd_smb2_dispatch_table {
	uint16_t opcode;
	uint16_t fileid_ofs;
	bool need_session : 1;
	bool need_tcon : 1;
	bool as_root : 1;
	bool modify : 1;
} smbd_smb2_table[] = {
	{
		.opcode = SMB2_OP_NEGPROT,
		.as_root = true,
	},{
		.opcode = SMB2_OP_SESSSETUP,
		.as_root = true,
	},{
		.opcode = SMB2_OP_LOGOFF,
		.need_session = true,
		.as_root = true,
	},{
		.opcode = SMB2_OP_TCON,
		.need_session = true,
		/*
		 * This call needs to be run as root.
		 *
		 * smbd_smb2_request_process_tcon()
		 * calls make_connection_snum(), which will call
		 * change_to_user(), when needed.
		 */
		.as_root = true,
	},{
		.opcode = SMB2_OP_TDIS,
		.need_session = true,
		.need_tcon = true,
		.as_root = true,
	},{
		.opcode = SMB2_OP_CREATE,
		.need_session = true,
		.need_tcon = true,
	},{
		.opcode = SMB2_OP_CLOSE,
		.need_session = true,
		.need_tcon = true,
		.fileid_ofs = 0x08,
	},{
		.opcode = SMB2_OP_FLUSH,
		.need_session = true,
		.need_tcon = true,
		.fileid_ofs = 0x08,
	},{
		.opcode = SMB2_OP_READ,
		.need_session = true,
		.need_tcon = true,
		.fileid_ofs = 0x10,
	},{
		.opcode = SMB2_OP_WRITE,
		.need_session = true,
		.need_tcon = true,
		.fileid_ofs = 0x10,
		.modify = true,
	},{
		.opcode = SMB2_OP_LOCK,
		.need_session = true,
		.need_tcon = true,
		.fileid_ofs = 0x08,
	},{
		.opcode = SMB2_OP_IOCTL,
		.need_session = true,
		.need_tcon = true,
		.fileid_ofs = 0x08,
		.modify = true,
	},{
		.opcode = SMB2_OP_CANCEL,
		.as_root = true,
	},{
		.opcode = SMB2_OP_KEEPALIVE,
	},{
		.opcode = SMB2_OP_QUERY_DIRECTORY,
		.need_session = true,
		.need_tcon = true,
		.fileid_ofs = 0x08,
	},{
		.opcode = SMB2_OP_NOTIFY,
		.need_session = true,
		.need_tcon = true,
		.fileid_ofs = 0x08,
	},{
		.opcode = SMB2_OP_GETINFO,
		.need_session = true,
		.need_tcon = true,
		.fileid_ofs = 0x18,
	},{
		.opcode = SMB2_OP_SETINFO,
		.need_session = true,
		.need_tcon = true,
		.fileid_ofs = 0x10,
		.modify = true,
	},{
		.opcode = SMB2_OP_BREAK,
		.need_session = true,
		.need_tcon = true,
		/*
		 * we do not set
		 * .fileid_ofs here
		 * as LEASE breaks does not
		 * have a file id
		 */
	}
};

const char *smb2_opcode_name(uint16_t opcode)
{
	const char *result = "Bad SMB2 opcode";

	switch (opcode) {
	case SMB2_OP_NEGPROT:
		result = "SMB2_OP_NEGPROT";
		break;
	case SMB2_OP_SESSSETUP:
		result = "SMB2_OP_SESSSETUP";
		break;
	case SMB2_OP_LOGOFF:
		result = "SMB2_OP_LOGOFF";
		break;
	case SMB2_OP_TCON:
		result = "SMB2_OP_TCON";
		break;
	case SMB2_OP_TDIS:
		result = "SMB2_OP_TDIS";
		break;
	case SMB2_OP_CREATE:
		result = "SMB2_OP_CREATE";
		break;
	case SMB2_OP_CLOSE:
		result = "SMB2_OP_CLOSE";
		break;
	case SMB2_OP_FLUSH:
		result = "SMB2_OP_FLUSH";
		break;
	case SMB2_OP_READ:
		result = "SMB2_OP_READ";
		break;
	case SMB2_OP_WRITE:
		result = "SMB2_OP_WRITE";
		break;
	case SMB2_OP_LOCK:
		result = "SMB2_OP_LOCK";
		break;
	case SMB2_OP_IOCTL:
		result = "SMB2_OP_IOCTL";
		break;
	case SMB2_OP_CANCEL:
		result = "SMB2_OP_CANCEL";
		break;
	case SMB2_OP_KEEPALIVE:
		result = "SMB2_OP_KEEPALIVE";
		break;
	case SMB2_OP_QUERY_DIRECTORY:
		result = "SMB2_OP_QUERY_DIRECTORY";
		break;
	case SMB2_OP_NOTIFY:
		result = "SMB2_OP_NOTIFY";
		break;
	case SMB2_OP_GETINFO:
		result = "SMB2_OP_GETINFO";
		break;
	case SMB2_OP_SETINFO:
		result = "SMB2_OP_SETINFO";
		break;
	case SMB2_OP_BREAK:
		result = "SMB2_OP_BREAK";
		break;
	default:
		break;
	}
	return result;
}

static const struct smbd_smb2_dispatch_table *smbd_smb2_call(uint16_t opcode)
{
	const struct smbd_smb2_dispatch_table *ret = NULL;

	if (opcode >= ARRAY_SIZE(smbd_smb2_table)) {
		return NULL;
	}

	ret = &smbd_smb2_table[opcode];

	SMB_ASSERT(ret->opcode == opcode);

	return ret;
}

static void print_req_vectors(const struct smbd_smb2_request *req)
{
	int i;

	for (i = 0; i < req->in.vector_count; i++) {
		dbgtext("\treq->in.vector[%u].iov_len = %u\n",
			(unsigned int)i,
			(unsigned int)req->in.vector[i].iov_len);
	}
	for (i = 0; i < req->out.vector_count; i++) {
		dbgtext("\treq->out.vector[%u].iov_len = %u\n",
			(unsigned int)i,
			(unsigned int)req->out.vector[i].iov_len);
	}
}

bool smbd_is_smb2_header(const uint8_t *inbuf, size_t size)
{
	if (size < (4 + SMB2_HDR_BODY)) {
		return false;
	}

	if (IVAL(inbuf, 4) != SMB2_MAGIC) {
		return false;
	}

	return true;
}

bool smbd_smb2_is_compound(const struct smbd_smb2_request *req)
{
	return req->in.vector_count >= (2*SMBD_SMB2_NUM_IOV_PER_REQ);
}

bool smbd_smb2_is_last_in_compound(const struct smbd_smb2_request *req)
{
	return (req->current_idx + SMBD_SMB2_NUM_IOV_PER_REQ ==
		req->in.vector_count);
}

static NTSTATUS smbd_initialize_smb2(struct smbXsrv_connection *xconn,
				     uint64_t expected_seq_low)
{
	int rc;

	xconn->smb2.credits.seq_low = expected_seq_low;
	xconn->smb2.credits.seq_range = 1;
	xconn->smb2.credits.granted = 1;
	xconn->smb2.credits.max = lp_smb2_max_credits();
	xconn->smb2.credits.bitmap = bitmap_talloc(xconn,
						   xconn->smb2.credits.max);
	if (xconn->smb2.credits.bitmap == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	tevent_fd_set_close_fn(xconn->transport.fde, NULL);
	TALLOC_FREE(xconn->transport.fde);

	xconn->transport.fde = tevent_add_fd(
					xconn->client->raw_ev_ctx,
					xconn,
					xconn->transport.sock,
					TEVENT_FD_ERROR | TEVENT_FD_READ,
					smbd_smb2_connection_handler,
					xconn);
	if (xconn->transport.fde == NULL) {
		close(xconn->transport.sock);
		xconn->transport.sock = -1;
		return NT_STATUS_NO_MEMORY;
	}
	tevent_fd_set_auto_close(xconn->transport.fde);

	/*
	 * Ensure child is set to non-blocking mode,
	 * unless the system supports MSG_DONTWAIT,
	 * if MSG_DONTWAIT is available we should force
	 * blocking mode.
	 */
#ifdef MSG_DONTWAIT
	rc = set_blocking(xconn->transport.sock, true);
	if (rc < 0) {
		return NT_STATUS_INTERNAL_ERROR;
	}
#else
	rc = set_blocking(xconn->transport.sock, false);
	if (rc < 0) {
		return NT_STATUS_INTERNAL_ERROR;
	}
#endif

	return NT_STATUS_OK;
}

#define smb2_len(buf) (PVAL(buf,3)|(PVAL(buf,2)<<8)|(PVAL(buf,1)<<16))
#define _smb2_setlen(_buf,len) do { \
	uint8_t *buf = (uint8_t *)_buf; \
	buf[0] = 0; \
	buf[1] = ((len)&0xFF0000)>>16; \
	buf[2] = ((len)&0xFF00)>>8; \
	buf[3] = (len)&0xFF; \
} while (0)

static bool smb2_setup_nbt_length(struct iovec *vector, int count)
{
	ssize_t len;

	if (count == 0) {
		return false;
	}

	len = iov_buflen(vector+1, count-1);

	if ((len == -1) || (len > 0xFFFFFF)) {
		return false;
	}

	_smb2_setlen(vector[0].iov_base, len);
	return true;
}

static int smbd_smb2_request_destructor(struct smbd_smb2_request *req)
{
	TALLOC_FREE(req->first_enc_key);
	TALLOC_FREE(req->last_sign_key);
	return 0;
}

void smb2_request_set_async_internal(struct smbd_smb2_request *req,
				     bool async_internal)
{
	req->async_internal = async_internal;
}

static struct smbd_smb2_request *smbd_smb2_request_allocate(struct smbXsrv_connection *xconn)
{
	TALLOC_CTX *mem_pool;
	struct smbd_smb2_request *req;

#if 0
	/* Enable this to find subtle valgrind errors. */
	mem_pool = talloc_init("smbd_smb2_request_allocate");
#else
	mem_pool = talloc_tos();
#endif
	if (mem_pool == NULL) {
		return NULL;
	}

	req = talloc(mem_pool, struct smbd_smb2_request);
	if (req == NULL) {
		talloc_free(mem_pool);
		return NULL;
	}
	talloc_reparent(mem_pool, xconn, req);
#if 0
	TALLOC_FREE(mem_pool);
#endif
	*req = (struct smbd_smb2_request) {
		.sconn = xconn->client->sconn,
		.xconn = xconn,
		.last_session_id = UINT64_MAX,
		.last_tid = UINT32_MAX,
	};

	talloc_set_destructor(req, smbd_smb2_request_destructor);

	return req;
}

static NTSTATUS smbd_smb2_inbuf_parse_compound(struct smbXsrv_connection *xconn,
					       NTTIME now,
					       uint8_t *buf,
					       size_t buflen,
					       struct smbd_smb2_request *req,
					       struct iovec **piov,
					       int *pnum_iov)
{
	TALLOC_CTX *mem_ctx = req;
	struct iovec *iov;
	int num_iov = 1;
	size_t taken = 0;
	uint8_t *first_hdr = buf;
	size_t verified_buflen = 0;
	uint8_t *tf = NULL;
	size_t tf_len = 0;

	/*
	 * Note: index '0' is reserved for the transport protocol
	 */
	iov = req->in._vector;

	while (taken < buflen) {
		size_t len = buflen - taken;
		uint8_t *hdr = first_hdr + taken;
		struct iovec *cur;
		size_t full_size;
		size_t next_command_ofs;
		uint16_t body_size;
		uint8_t *body = NULL;
		uint32_t dyn_size;
		uint8_t *dyn = NULL;
		struct iovec *iov_alloc = NULL;

		if (iov != req->in._vector) {
			iov_alloc = iov;
		}

		if (verified_buflen > taken) {
			len = verified_buflen - taken;
		} else {
			tf = NULL;
			tf_len = 0;
		}

		if (len < 4) {
			DEBUG(10, ("%d bytes left, expected at least %d\n",
				   (int)len, 4));
			goto inval;
		}
		if (IVAL(hdr, 0) == SMB2_TF_MAGIC) {
			struct smbXsrv_session *s = NULL;
			uint64_t uid;
			struct iovec tf_iov[2];
			NTSTATUS status;
			size_t enc_len;

			if (xconn->protocol < PROTOCOL_SMB3_00) {
				DEBUG(10, ("Got SMB2_TRANSFORM header, "
					   "but dialect[0x%04X] is used\n",
					   xconn->smb2.server.dialect));
				goto inval;
			}

			if (xconn->smb2.server.cipher == 0) {
				DEBUG(10, ("Got SMB2_TRANSFORM header, "
					   "but not negotiated "
					   "client[0x%08X] server[0x%08X]\n",
					   xconn->smb2.client.capabilities,
					   xconn->smb2.server.capabilities));
				goto inval;
			}

			if (!xconn->smb2.got_authenticated_session) {
				D_INFO("Got SMB2_TRANSFORM header, "
				       "but not no authenticated session yet "
				       "client[%s] server[%s]\n",
				       tsocket_address_string(
					xconn->remote_address, talloc_tos()),
				       tsocket_address_string(
					xconn->local_address, talloc_tos()));
				goto inval;
			}

			if (len < SMB2_TF_HDR_SIZE) {
				DEBUG(1, ("%d bytes left, expected at least %d\n",
					   (int)len, SMB2_TF_HDR_SIZE));
				goto inval;
			}
			tf = hdr;
			tf_len = SMB2_TF_HDR_SIZE;
			taken += tf_len;

			hdr = first_hdr + taken;
			enc_len = IVAL(tf, SMB2_TF_MSG_SIZE);
			uid = BVAL(tf, SMB2_TF_SESSION_ID);

			if (len < SMB2_TF_HDR_SIZE + enc_len) {
				DEBUG(1, ("%d bytes left, expected at least %d\n",
					   (int)len,
					   (int)(SMB2_TF_HDR_SIZE + enc_len)));
				goto inval;
			}

			status = smb2srv_session_lookup_conn(xconn, uid, now,
							     &s);
			if (!NT_STATUS_IS_OK(status)) {
				status = smb2srv_session_lookup_global(xconn->client,
								       uid, req, &s);
			}
			if (!NT_STATUS_IS_OK(status)) {
				DBG_WARNING("invalid session[%" PRIu64 "] in "
					    "SMB2_TRANSFORM header\n",
					    uid);
				TALLOC_FREE(iov_alloc);
				return NT_STATUS_USER_SESSION_DELETED;
			}

			tf_iov[0].iov_base = (void *)tf;
			tf_iov[0].iov_len = tf_len;
			tf_iov[1].iov_base = (void *)hdr;
			tf_iov[1].iov_len = enc_len;

			status = smb2_signing_decrypt_pdu(s->global->decryption_key,
							  tf_iov, 2);
			if (!NT_STATUS_IS_OK(status)) {
				TALLOC_FREE(iov_alloc);
				return status;
			}

			verified_buflen = taken + enc_len;
			len = enc_len;
		}

		/*
		 * We need the header plus the body length field
		 */

		if (len < SMB2_HDR_BODY + 2) {

			if ((len == 5) &&
			    (IVAL(hdr, 0) == SMB_SUICIDE_PACKET) &&
			    lp_parm_bool(-1, "smbd", "suicide mode", false)) {
				uint8_t exitcode = CVAL(hdr, 4);
				DBG_WARNING("SUICIDE: Exiting immediately "
					    "with code %"PRIu8"\n",
					    exitcode);
				exit(exitcode);
			}

			DEBUG(10, ("%d bytes left, expected at least %d\n",
				   (int)len, SMB2_HDR_BODY));
			goto inval;
		}
		if (IVAL(hdr, 0) != SMB2_MAGIC) {
			DEBUG(10, ("Got non-SMB2 PDU: %x\n",
				   IVAL(hdr, 0)));
			goto inval;
		}
		if (SVAL(hdr, 4) != SMB2_HDR_BODY) {
			DEBUG(10, ("Got HDR len %d, expected %d\n",
				   SVAL(hdr, 4), SMB2_HDR_BODY));
			goto inval;
		}

		full_size = len;
		next_command_ofs = IVAL(hdr, SMB2_HDR_NEXT_COMMAND);
		body_size = SVAL(hdr, SMB2_HDR_BODY);

		if (next_command_ofs != 0) {
			if (next_command_ofs < (SMB2_HDR_BODY + 2)) {
				goto inval;
			}
			if (next_command_ofs > full_size) {
				goto inval;
			}
			full_size = next_command_ofs;
		}
		if (body_size < 2) {
			goto inval;
		}
		body_size &= 0xfffe;

		if (body_size > (full_size - SMB2_HDR_BODY)) {
			/*
			 * let the caller handle the error
			 */
			body_size = full_size - SMB2_HDR_BODY;
		}
		body = hdr + SMB2_HDR_BODY;
		dyn = body + body_size;
		dyn_size = full_size - (SMB2_HDR_BODY + body_size);

		if (num_iov >= ARRAY_SIZE(req->in._vector)) {
			struct iovec *iov_tmp = NULL;

			iov_tmp = talloc_realloc(mem_ctx, iov_alloc,
						 struct iovec,
						 num_iov +
						 SMBD_SMB2_NUM_IOV_PER_REQ);
			if (iov_tmp == NULL) {
				TALLOC_FREE(iov_alloc);
				return NT_STATUS_NO_MEMORY;
			}

			if (iov_alloc == NULL) {
				memcpy(iov_tmp,
				       req->in._vector,
				       sizeof(req->in._vector));
			}

			iov = iov_tmp;
		}
		cur = &iov[num_iov];
		num_iov += SMBD_SMB2_NUM_IOV_PER_REQ;

		cur[SMBD_SMB2_TF_IOV_OFS].iov_base   = tf;
		cur[SMBD_SMB2_TF_IOV_OFS].iov_len    = tf_len;
		cur[SMBD_SMB2_HDR_IOV_OFS].iov_base  = hdr;
		cur[SMBD_SMB2_HDR_IOV_OFS].iov_len   = SMB2_HDR_BODY;
		cur[SMBD_SMB2_BODY_IOV_OFS].iov_base = body;
		cur[SMBD_SMB2_BODY_IOV_OFS].iov_len  = body_size;
		cur[SMBD_SMB2_DYN_IOV_OFS].iov_base  = dyn;
		cur[SMBD_SMB2_DYN_IOV_OFS].iov_len   = dyn_size;

		taken += full_size;
	}

	*piov = iov;
	*pnum_iov = num_iov;
	return NT_STATUS_OK;

inval:
	if (iov != req->in._vector) {
		TALLOC_FREE(iov);
	}
	return NT_STATUS_INVALID_PARAMETER;
}

static NTSTATUS smbd_smb2_request_create(struct smbXsrv_connection *xconn,
					 const uint8_t *_inpdu, size_t size,
					 struct smbd_smb2_request **_req)
{
	struct smbd_smb2_request *req;
	uint32_t protocol_version;
	uint8_t *inpdu = NULL;
	const uint8_t *inhdr = NULL;
	uint16_t cmd;
	uint32_t next_command_ofs;
	NTSTATUS status;
	NTTIME now;

	if (size < (SMB2_HDR_BODY + 2)) {
		DEBUG(0,("Invalid SMB2 packet length count %ld\n", (long)size));
		return NT_STATUS_INVALID_PARAMETER;
	}

	inhdr = _inpdu;

	protocol_version = IVAL(inhdr, SMB2_HDR_PROTOCOL_ID);
	if (protocol_version != SMB2_MAGIC) {
		DEBUG(0,("Invalid SMB packet: protocol prefix: 0x%08X\n",
			 protocol_version));
		return NT_STATUS_INVALID_PARAMETER;
	}

	cmd = SVAL(inhdr, SMB2_HDR_OPCODE);
	if (cmd != SMB2_OP_NEGPROT) {
		DEBUG(0,("Invalid SMB packet: first request: 0x%04X\n",
			 cmd));
		return NT_STATUS_INVALID_PARAMETER;
	}

	next_command_ofs = IVAL(inhdr, SMB2_HDR_NEXT_COMMAND);
	if (next_command_ofs != 0) {
		DEBUG(0,("Invalid SMB packet: next_command: 0x%08X\n",
			 next_command_ofs));
		return NT_STATUS_INVALID_PARAMETER;
	}

	req = smbd_smb2_request_allocate(xconn);
	if (req == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	inpdu = talloc_memdup(req, _inpdu, size);
	if (inpdu == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	req->request_time = timeval_current();
	now = timeval_to_nttime(&req->request_time);

	status = smbd_smb2_inbuf_parse_compound(xconn,
						now,
						inpdu,
						size,
						req, &req->in.vector,
						&req->in.vector_count);
	if (!NT_STATUS_IS_OK(status)) {
		TALLOC_FREE(req);
		return status;
	}

	req->current_idx = 1;

	*_req = req;
	return NT_STATUS_OK;
}

static bool smb2_validate_sequence_number(struct smbXsrv_connection *xconn,
					  uint64_t message_id, uint64_t seq_id)
{
	struct bitmap *credits_bm = xconn->smb2.credits.bitmap;
	unsigned int offset;
	uint64_t seq_tmp;

	seq_tmp = xconn->smb2.credits.seq_low;
	if (seq_id < seq_tmp) {
		DBGC_ERR(DBGC_SMB2_CREDITS,
			"smb2_validate_sequence_number: bad message_id "
			"%llu (sequence id %llu) "
			"(granted = %u, low = %llu, range = %u)\n",
			(unsigned long long)message_id,
			(unsigned long long)seq_id,
			(unsigned int)xconn->smb2.credits.granted,
			(unsigned long long)xconn->smb2.credits.seq_low,
			(unsigned int)xconn->smb2.credits.seq_range);
		return false;
	}

	seq_tmp += xconn->smb2.credits.seq_range;
	if (seq_id >= seq_tmp) {
		DBGC_ERR(DBGC_SMB2_CREDITS,
			"smb2_validate_sequence_number: bad message_id "
			"%llu (sequence id %llu) "
			"(granted = %u, low = %llu, range = %u)\n",
			(unsigned long long)message_id,
			(unsigned long long)seq_id,
			(unsigned int)xconn->smb2.credits.granted,
			(unsigned long long)xconn->smb2.credits.seq_low,
			(unsigned int)xconn->smb2.credits.seq_range);
		return false;
	}

	offset = seq_id % xconn->smb2.credits.max;

	if (bitmap_query(credits_bm, offset)) {
		DBGC_ERR(DBGC_SMB2_CREDITS,
			"smb2_validate_sequence_number: duplicate message_id "
			"%llu (sequence id %llu) "
			"(granted = %u, low = %llu, range = %u) "
			"(bm offset %u)\n",
			(unsigned long long)message_id,
			(unsigned long long)seq_id,
			(unsigned int)xconn->smb2.credits.granted,
			(unsigned long long)xconn->smb2.credits.seq_low,
			(unsigned int)xconn->smb2.credits.seq_range,
			offset);
		return false;
	}

	/* Mark the message_ids as seen in the bitmap. */
	bitmap_set(credits_bm, offset);

	if (seq_id != xconn->smb2.credits.seq_low) {
		return true;
	}

	/*
	 * Move the window forward by all the message_id's
	 * already seen.
	 */
	while (bitmap_query(credits_bm, offset)) {
		DBGC_DEBUG(DBGC_SMB2_CREDITS,
			  "smb2_validate_sequence_number: clearing "
			  "id %llu (position %u) from bitmap\n",
			  (unsigned long long)(xconn->smb2.credits.seq_low),
			  offset);
		bitmap_clear(credits_bm, offset);

		xconn->smb2.credits.seq_low += 1;
		xconn->smb2.credits.seq_range -= 1;
		offset = xconn->smb2.credits.seq_low % xconn->smb2.credits.max;
	}

	return true;
}

static bool smb2_validate_message_id(struct smbXsrv_connection *xconn,
				     const uint8_t *inhdr)
{
	uint64_t message_id = BVAL(inhdr, SMB2_HDR_MESSAGE_ID);
	uint16_t opcode = SVAL(inhdr, SMB2_HDR_OPCODE);
	uint16_t credit_charge = 1;
	uint64_t i;

	if (opcode == SMB2_OP_CANCEL) {
		/* SMB2_CANCEL requests by definition resend messageids. */
		return true;
	}

	if (xconn->smb2.credits.multicredit) {
		credit_charge = SVAL(inhdr, SMB2_HDR_CREDIT_CHARGE);
		credit_charge = MAX(credit_charge, 1);
	}

	DEBUGC(11,
		   DBGC_SMB2_CREDITS,
		   ("smb2_validate_message_id: mid %llu (charge %llu), "
		   "credits_granted %llu, "
		   "seqnum low/range: %llu/%llu\n",
		   (unsigned long long) message_id,
		   (unsigned long long) credit_charge,
		   (unsigned long long) xconn->smb2.credits.granted,
		   (unsigned long long) xconn->smb2.credits.seq_low,
		   (unsigned long long) xconn->smb2.credits.seq_range));

	if (xconn->smb2.credits.granted < credit_charge) {
		DBGC_ERR(DBGC_SMB2_CREDITS,
			  "smb2_validate_message_id: client used more "
			  "credits than granted, mid %llu, charge %llu, "
			  "credits_granted %llu, "
			  "seqnum low/range: %llu/%llu\n",
			  (unsigned long long) message_id,
			  (unsigned long long) credit_charge,
			  (unsigned long long) xconn->smb2.credits.granted,
			  (unsigned long long) xconn->smb2.credits.seq_low,
			  (unsigned long long) xconn->smb2.credits.seq_range);
		return false;
	}

	/*
	 * now check the message ids
	 *
	 * for multi-credit requests we need to check all current mid plus
	 * the implicit mids caused by the credit charge
	 * e.g. current mid = 15, charge 5 => mark 15-19 as used
	 */

	for (i = 0; i <= (credit_charge-1); i++) {
		uint64_t id = message_id + i;
		bool ok;

		DEBUGC(11,
			   DBGC_SMB2_CREDITS,
			   ("Iterating mid %llu charge %u (sequence %llu)\n",
			   (unsigned long long)message_id,
			   credit_charge,
			   (unsigned long long)id));

		ok = smb2_validate_sequence_number(xconn, message_id, id);
		if (!ok) {
			return false;
		}
	}

	/* subtract used credits */
	xconn->smb2.credits.granted -= credit_charge;

	return true;
}

static NTSTATUS smbd_smb2_request_validate(struct smbd_smb2_request *req)
{
	int count;
	int idx;

	count = req->in.vector_count;

	if (count < 1 + SMBD_SMB2_NUM_IOV_PER_REQ) {
		/* It's not a SMB2 request */
		return NT_STATUS_INVALID_PARAMETER;
	}

	for (idx=1; idx < count; idx += SMBD_SMB2_NUM_IOV_PER_REQ) {
		struct iovec *hdr = SMBD_SMB2_IDX_HDR_IOV(req,in,idx);
		struct iovec *body = SMBD_SMB2_IDX_BODY_IOV(req,in,idx);
		const uint8_t *inhdr = NULL;

		if (hdr->iov_len != SMB2_HDR_BODY) {
			return NT_STATUS_INVALID_PARAMETER;
		}

		if (body->iov_len < 2) {
			return NT_STATUS_INVALID_PARAMETER;
		}

		inhdr = (const uint8_t *)hdr->iov_base;

		/* Check the SMB2 header */
		if (IVAL(inhdr, SMB2_HDR_PROTOCOL_ID) != SMB2_MAGIC) {
			return NT_STATUS_INVALID_PARAMETER;
		}

		if (!smb2_validate_message_id(req->xconn, inhdr)) {
			return NT_STATUS_INVALID_PARAMETER;
		}
	}

	return NT_STATUS_OK;
}

static void smb2_set_operation_credit(struct smbXsrv_connection *xconn,
				      const struct iovec *in_vector,
				      struct iovec *out_vector)
{
	const uint8_t *inhdr = (const uint8_t *)in_vector->iov_base;
	uint8_t *outhdr = (uint8_t *)out_vector->iov_base;
	uint16_t credit_charge = 1;
	uint16_t credits_requested;
	uint32_t out_flags;
	uint16_t cmd;
	NTSTATUS out_status;
	uint16_t credits_granted = 0;
	uint64_t credits_possible;
	uint16_t current_max_credits;

	/*
	 * first we grant only 1/16th of the max range.
	 *
	 * Windows also starts with the 1/16th and then grants
	 * more later. I was only able to trigger higher
	 * values, when using a very high credit charge.
	 *
	 * TODO: scale up depending on load, free memory
	 *       or other stuff.
	 *       Maybe also on the relationship between number
	 *       of requests and the used sequence number.
	 *       Which means we would grant more credits
	 *       for client which use multi credit requests.
	 *
	 * The above is what Windows Server < 2016 is doing,
	 * but new servers use all credits (8192 by default).
	 */
	current_max_credits = xconn->smb2.credits.max;
	current_max_credits = MAX(current_max_credits, 1);

	if (xconn->smb2.credits.multicredit) {
		credit_charge = SVAL(inhdr, SMB2_HDR_CREDIT_CHARGE);
		credit_charge = MAX(credit_charge, 1);
	}

	cmd = SVAL(inhdr, SMB2_HDR_OPCODE);
	credits_requested = SVAL(inhdr, SMB2_HDR_CREDIT);
	credits_requested = MAX(credits_requested, 1);
	out_flags = IVAL(outhdr, SMB2_HDR_FLAGS);
	out_status = NT_STATUS(IVAL(outhdr, SMB2_HDR_STATUS));

	SMB_ASSERT(xconn->smb2.credits.max >= xconn->smb2.credits.granted);

	if (xconn->smb2.credits.max < credit_charge) {
		smbd_server_connection_terminate(xconn,
			"client error: credit charge > max credits\n");
		return;
	}

	if (out_flags & SMB2_HDR_FLAG_ASYNC) {
		/*
		 * In case we already send an async interim
		 * response, we should not grant
		 * credits on the final response.
		 */
		credits_granted = 0;
	} else {
		uint16_t additional_possible =
			xconn->smb2.credits.max - credit_charge;
		uint16_t additional_max = 0;
		uint16_t additional_credits = credits_requested - 1;

		switch (cmd) {
		case SMB2_OP_NEGPROT:
			break;
		case SMB2_OP_SESSSETUP:
			/*
			 * Windows 2012 RC1 starts to grant
			 * additional credits
			 * with a successful session setup
			 */
			if (NT_STATUS_IS_OK(out_status)) {
				additional_max = xconn->smb2.credits.max;
			}
			break;
		default:
			/*
			 * Windows Server < 2016 and older Samba versions
			 * used to only grant additional credits in
			 * chunks of 32 credits.
			 *
			 * But we match Windows Server 2016 and grant
			 * all credits as requested.
			 */
			additional_max = xconn->smb2.credits.max;
			break;
		}

		additional_max = MIN(additional_max, additional_possible);
		additional_credits = MIN(additional_credits, additional_max);

		credits_granted = credit_charge + additional_credits;
	}

	/*
	 * sequence numbers should not wrap
	 *
	 * 1. calculate the possible credits until
	 *    the sequence numbers start to wrap on 64-bit.
	 *
	 * 2. UINT64_MAX is used for Break Notifications.
	 *
	 * 2. truncate the possible credits to the maximum
	 *    credits we want to grant to the client in total.
	 *
	 * 3. remove the range we'll already granted to the client
	 *    this makes sure the client consumes the lowest sequence
	 *    number, before we can grant additional credits.
	 */
	credits_possible = UINT64_MAX - xconn->smb2.credits.seq_low;
	if (credits_possible > 0) {
		/* remove UINT64_MAX */
		credits_possible -= 1;
	}
	credits_possible = MIN(credits_possible, current_max_credits);
	credits_possible -= xconn->smb2.credits.seq_range;

	credits_granted = MIN(credits_granted, credits_possible);

	SSVAL(outhdr, SMB2_HDR_CREDIT, credits_granted);
	xconn->smb2.credits.granted += credits_granted;
	xconn->smb2.credits.seq_range += credits_granted;

	DBGC_DEBUG(DBGC_SMB2_CREDITS,
		"smb2_set_operation_credit: requested %u, charge %u, "
		"granted %u, current possible/max %u/%u, "
		"total granted/max/low/range %u/%u/%llu/%u\n",
		(unsigned int)credits_requested,
		(unsigned int)credit_charge,
		(unsigned int)credits_granted,
		(unsigned int)credits_possible,
		(unsigned int)current_max_credits,
		(unsigned int)xconn->smb2.credits.granted,
		(unsigned int)xconn->smb2.credits.max,
		(unsigned long long)xconn->smb2.credits.seq_low,
		(unsigned int)xconn->smb2.credits.seq_range);
}

static void smb2_calculate_credits(const struct smbd_smb2_request *inreq,
				struct smbd_smb2_request *outreq)
{
	int count, idx;
	uint16_t total_credits = 0;

	count = outreq->out.vector_count;

	for (idx=1; idx < count; idx += SMBD_SMB2_NUM_IOV_PER_REQ) {
		struct iovec *inhdr_v = SMBD_SMB2_IDX_HDR_IOV(inreq,in,idx);
		struct iovec *outhdr_v = SMBD_SMB2_IDX_HDR_IOV(outreq,out,idx);
		uint8_t *outhdr = (uint8_t *)outhdr_v->iov_base;

		smb2_set_operation_credit(outreq->xconn, inhdr_v, outhdr_v);

		/* To match Windows, count up what we
		   just granted. */
		total_credits += SVAL(outhdr, SMB2_HDR_CREDIT);
		/* Set to zero in all but the last reply. */
		if (idx + SMBD_SMB2_NUM_IOV_PER_REQ < count) {
			SSVAL(outhdr, SMB2_HDR_CREDIT, 0);
		} else {
			SSVAL(outhdr, SMB2_HDR_CREDIT, total_credits);
		}
	}
}

DATA_BLOB smbd_smb2_generate_outbody(struct smbd_smb2_request *req, size_t size)
{
	if (req->current_idx <= 1) {
		if (size <= sizeof(req->out._body)) {
			return data_blob_const(req->out._body, size);
		}
	}

	return data_blob_talloc(req, NULL, size);
}

static NTSTATUS smbd_smb2_request_setup_out(struct smbd_smb2_request *req)
{
	struct smbXsrv_connection *xconn = req->xconn;
	TALLOC_CTX *mem_ctx;
	struct iovec *vector;
	int count;
	int idx;
	bool ok;

	count = req->in.vector_count;
	if (count <= ARRAY_SIZE(req->out._vector)) {
		mem_ctx = req;
		vector = req->out._vector;
	} else {
		vector = talloc_zero_array(req, struct iovec, count);
		if (vector == NULL) {
			return NT_STATUS_NO_MEMORY;
		}
		mem_ctx = vector;
	}

	vector[0].iov_base	= req->out.nbt_hdr;
	vector[0].iov_len	= 4;
	SIVAL(req->out.nbt_hdr, 0, 0);

	for (idx=1; idx < count; idx += SMBD_SMB2_NUM_IOV_PER_REQ) {
		struct iovec *inhdr_v = SMBD_SMB2_IDX_HDR_IOV(req,in,idx);
		const uint8_t *inhdr = (const uint8_t *)inhdr_v->iov_base;
		uint8_t *outhdr = NULL;
		uint8_t *outbody = NULL;
		uint32_t next_command_ofs = 0;
		struct iovec *current = &vector[idx];

		if ((idx + SMBD_SMB2_NUM_IOV_PER_REQ) < count) {
			/* we have a next command -
			 * setup for the error case. */
			next_command_ofs = SMB2_HDR_BODY + 9;
		}

		if (idx == 1) {
			outhdr = req->out._hdr;
		} else {
			outhdr = talloc_zero_array(mem_ctx, uint8_t,
						   OUTVEC_ALLOC_SIZE);
			if (outhdr == NULL) {
				return NT_STATUS_NO_MEMORY;
			}
		}

		outbody = outhdr + SMB2_HDR_BODY;

		/*
		 * SMBD_SMB2_TF_IOV_OFS might be used later
		 */
		current[SMBD_SMB2_TF_IOV_OFS].iov_base   = NULL;
		current[SMBD_SMB2_TF_IOV_OFS].iov_len    = 0;

		current[SMBD_SMB2_HDR_IOV_OFS].iov_base  = (void *)outhdr;
		current[SMBD_SMB2_HDR_IOV_OFS].iov_len   = SMB2_HDR_BODY;

		current[SMBD_SMB2_BODY_IOV_OFS].iov_base = (void *)outbody;
		current[SMBD_SMB2_BODY_IOV_OFS].iov_len  = 8;

		current[SMBD_SMB2_DYN_IOV_OFS].iov_base  = NULL;
		current[SMBD_SMB2_DYN_IOV_OFS].iov_len   = 0;

		/* setup the SMB2 header */
		SIVAL(outhdr, SMB2_HDR_PROTOCOL_ID,	SMB2_MAGIC);
		SSVAL(outhdr, SMB2_HDR_LENGTH,		SMB2_HDR_BODY);
		SSVAL(outhdr, SMB2_HDR_CREDIT_CHARGE,
		      SVAL(inhdr, SMB2_HDR_CREDIT_CHARGE));
		SIVAL(outhdr, SMB2_HDR_STATUS,
		      NT_STATUS_V(NT_STATUS_INTERNAL_ERROR));
		SSVAL(outhdr, SMB2_HDR_OPCODE,
		      SVAL(inhdr, SMB2_HDR_OPCODE));
		SIVAL(outhdr, SMB2_HDR_FLAGS,
		      IVAL(inhdr, SMB2_HDR_FLAGS) | SMB2_HDR_FLAG_REDIRECT);
		SIVAL(outhdr, SMB2_HDR_NEXT_COMMAND,	next_command_ofs);
		SBVAL(outhdr, SMB2_HDR_MESSAGE_ID,
		      BVAL(inhdr, SMB2_HDR_MESSAGE_ID));
		SIVAL(outhdr, SMB2_HDR_PID,
		      IVAL(inhdr, SMB2_HDR_PID));
		SIVAL(outhdr, SMB2_HDR_TID,
		      IVAL(inhdr, SMB2_HDR_TID));
		SBVAL(outhdr, SMB2_HDR_SESSION_ID,
		      BVAL(inhdr, SMB2_HDR_SESSION_ID));
		memcpy(outhdr + SMB2_HDR_SIGNATURE,
		       inhdr + SMB2_HDR_SIGNATURE, 16);

		/* setup error body header */
		SSVAL(outbody, 0x00, 0x08 + 1);
		SSVAL(outbody, 0x02, 0);
		SIVAL(outbody, 0x04, 0);
	}

	req->out.vector = vector;
	req->out.vector_count = count;

	/* setup the length of the NBT packet */
	ok = smb2_setup_nbt_length(req->out.vector, req->out.vector_count);
	if (!ok) {
		return NT_STATUS_INVALID_PARAMETER_MIX;
	}

	DLIST_ADD_END(xconn->smb2.requests, req);

	return NT_STATUS_OK;
}

bool smbXsrv_server_multi_channel_enabled(void)
{
	bool enabled = lp_server_multi_channel_support();
#ifndef __ALLOW_MULTI_CHANNEL_SUPPORT
	bool forced = false;
	struct loadparm_context *lp_ctx = loadparm_init_s3(NULL, loadparm_s3_helpers());
	bool unspecified = lpcfg_parm_is_unspecified(lp_ctx, "server multi channel support");
	if (unspecified) {
		enabled = false;
	}
	/*
	 * If we don't have support from the kernel
	 * to ask for the un-acked number of bytes
	 * in the socket send queue, we better
	 * don't support multi-channel.
	 */
	forced = lp_parm_bool(-1, "force", "server multi channel support", false);
	if (enabled && !forced) {
		D_NOTICE("'server multi channel support' enabled "
			 "but not supported on %s (%s)\n",
			 SYSTEM_UNAME_SYSNAME, SYSTEM_UNAME_RELEASE);
		DEBUGADD(DBGLVL_NOTICE, ("Please report this on "
			"https://bugzilla.samba.org/show_bug.cgi?id=11897\n"));
		enabled = false;
	}
	TALLOC_FREE(lp_ctx);
#endif /* ! __ALLOW_MULTI_CHANNEL_SUPPORT */
	return enabled;
}

static NTSTATUS smbXsrv_connection_get_rto_usecs(struct smbXsrv_connection *xconn,
						 uint32_t *_rto_usecs)
{
	/*
	 * Define an Retransmission Timeout
	 * of 1 second, if there's no way for the
	 * kernel to tell us the current value.
	 */
	uint32_t rto_usecs = 1000000;

#ifdef __HAVE_TCP_INFO_RTO
	{
		struct tcp_info info;
		socklen_t ilen = sizeof(info);
		int ret;

		ZERO_STRUCT(info);
		ret = getsockopt(xconn->transport.sock,
				 IPPROTO_TCP, TCP_INFO,
				 (void *)&info, &ilen);
		if (ret != 0) {
			int saved_errno = errno;
			NTSTATUS status = map_nt_error_from_unix(errno);
			DBG_ERR("getsockopt(TCP_INFO) errno[%d/%s] -s %s\n",
				saved_errno, strerror(saved_errno),
				nt_errstr(status));
			return status;
		}

		DBG_DEBUG("tcpi_rto[%u] tcpi_rtt[%u] tcpi_rttvar[%u]\n",
			  (unsigned)info.tcpi_rto,
			  (unsigned)info.tcpi_rtt,
			  (unsigned)info.tcpi_rttvar);
		rto_usecs = info.tcpi_rto;
	}
#endif /* __HAVE_TCP_INFO_RTO */

	rto_usecs = MAX(rto_usecs,  200000); /* at least 0.2s */
	rto_usecs = MIN(rto_usecs, 1000000); /* at max   1.0s */
	*_rto_usecs = rto_usecs;
	return NT_STATUS_OK;
}

static NTSTATUS smbXsrv_connection_get_acked_bytes(struct smbXsrv_connection *xconn,
						   uint64_t *_acked_bytes)
{
	/*
	 * Unless the kernel has an interface
	 * to reveal the number of un-acked bytes
	 * in the socket send queue, we'll assume
	 * everything is already acked.
	 *
	 * But that would mean that we better don't
	 * pretent to support multi-channel.
	 */
	uint64_t unacked_bytes = 0;

	*_acked_bytes = 0;

	if (xconn->ack.force_unacked_timeout) {
		/*
		 * Smbtorture tries to test channel failures...
		 * Just pretend nothing was acked...
		 */
		DBG_INFO("Simulating channel failure: "
			 "xconn->ack.unacked_bytes[%llu]\n",
			 (unsigned long long)xconn->ack.unacked_bytes);
		return NT_STATUS_OK;
	}

#ifdef __IOCTL_SEND_QUEUE_SIZE_OPCODE
	{
		int value = 0;
		int ret;

		/*
		 * If we have kernel support to get
		 * the number of bytes waiting in
		 * the socket's send queue, we
		 * use that in order to find out
		 * the number of unacked bytes.
		 */
		ret = ioctl(xconn->transport.sock,
			    __IOCTL_SEND_QUEUE_SIZE_OPCODE,
			    &value);
		if (ret != 0) {
			int saved_errno = errno;
			NTSTATUS status = map_nt_error_from_unix(saved_errno);
			DBG_ERR("Failed to get the SEND_QUEUE_SIZE - "
				"errno %d (%s) - %s\n",
				saved_errno, strerror(saved_errno),
				nt_errstr(status));
			return status;
		}

		if (value < 0) {
			DBG_ERR("xconn->ack.unacked_bytes[%llu] value[%d]\n",
				(unsigned long long)xconn->ack.unacked_bytes,
				value);
			return NT_STATUS_INTERNAL_ERROR;
		}
		unacked_bytes = value;
	}
#endif
	if (xconn->ack.unacked_bytes == 0) {
		xconn->ack.unacked_bytes = unacked_bytes;
		return NT_STATUS_OK;
	}

	if (xconn->ack.unacked_bytes < unacked_bytes) {
		DBG_ERR("xconn->ack.unacked_bytes[%llu] unacked_bytes[%llu]\n",
			(unsigned long long)xconn->ack.unacked_bytes,
			(unsigned long long)unacked_bytes);
		return NT_STATUS_INTERNAL_ERROR;
	}

	*_acked_bytes = xconn->ack.unacked_bytes - unacked_bytes;
	xconn->ack.unacked_bytes = unacked_bytes;
	return NT_STATUS_OK;
}

static void smbd_smb2_send_queue_ack_fail(struct smbd_smb2_send_queue **queue,
					  NTSTATUS status)
{
	struct smbd_smb2_send_queue *e = NULL;
	struct smbd_smb2_send_queue *n = NULL;

	for (e = *queue; e != NULL; e = n) {
		n = e->next;

		DLIST_REMOVE(*queue, e);
		if (e->ack.req != NULL) {
			tevent_req_nterror(e->ack.req, status);
		}
	}
}

static NTSTATUS smbd_smb2_send_queue_ack_bytes(struct smbd_smb2_send_queue **queue,
					       uint64_t acked_bytes)
{
	struct smbd_smb2_send_queue *e = NULL;
	struct smbd_smb2_send_queue *n = NULL;

	for (e = *queue; e != NULL; e = n) {
		bool expired;

		n = e->next;

		if (e->ack.req == NULL) {
			continue;
		}

		if (e->ack.required_acked_bytes <= acked_bytes) {
			e->ack.required_acked_bytes = 0;
			DLIST_REMOVE(*queue, e);
			tevent_req_done(e->ack.req);
			continue;
		}
		e->ack.required_acked_bytes -= acked_bytes;

		expired = timeval_expired(&e->ack.timeout);
		if (expired) {
			return NT_STATUS_IO_TIMEOUT;
		}
	}

	return NT_STATUS_OK;
}

static NTSTATUS smbd_smb2_check_ack_queue(struct smbXsrv_connection *xconn)
{
	uint64_t acked_bytes = 0;
	NTSTATUS status;

	status = smbXsrv_connection_get_acked_bytes(xconn, &acked_bytes);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	status = smbd_smb2_send_queue_ack_bytes(&xconn->ack.queue, acked_bytes);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	status = smbd_smb2_send_queue_ack_bytes(&xconn->smb2.send_queue, 0);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	return NT_STATUS_OK;
}

static void smbXsrv_connection_ack_checker(struct tevent_req *subreq)
{
	struct smbXsrv_connection *xconn =
		tevent_req_callback_data(subreq,
		struct smbXsrv_connection);
	struct smbXsrv_client *client = xconn->client;
	struct timeval next_check;
	NTSTATUS status;
	bool ok;

	xconn->ack.checker_subreq = NULL;

	ok = tevent_wakeup_recv(subreq);
	TALLOC_FREE(subreq);
	if (!ok) {
		smbd_server_connection_terminate(xconn,
						 "tevent_wakeup_recv() failed");
		return;
	}

	status = smbd_smb2_check_ack_queue(xconn);
	if (!NT_STATUS_IS_OK(status)) {
		smbd_server_connection_terminate(xconn, nt_errstr(status));
		return;
	}

	next_check = timeval_current_ofs_usec(xconn->ack.rto_usecs);
	xconn->ack.checker_subreq = tevent_wakeup_send(xconn,
						       client->raw_ev_ctx,
						       next_check);
	if (xconn->ack.checker_subreq == NULL) {
		smbd_server_connection_terminate(xconn,
						 "tevent_wakeup_send() failed");
		return;
	}
	tevent_req_set_callback(xconn->ack.checker_subreq,
				smbXsrv_connection_ack_checker,
				xconn);
}

static NTSTATUS smbXsrv_client_pending_breaks_updated(struct smbXsrv_client *client)
{
	struct smbXsrv_connection *xconn = NULL;

	for (xconn = client->connections; xconn != NULL; xconn = xconn->next) {
		struct timeval next_check;
		uint64_t acked_bytes = 0;
		NTSTATUS status;

		/*
		 * A new 'pending break cycle' starts
		 * with a first pending break and lasts until
		 * all pending breaks are finished.
		 *
		 * This is typically a very short time,
		 * the value of one retransmission timeout.
		 */

		if (client->pending_breaks == NULL) {
			/*
			 * No more pending breaks, remove a pending
			 * checker timer
			 */
			TALLOC_FREE(xconn->ack.checker_subreq);
			continue;
		}

		if (xconn->ack.checker_subreq != NULL) {
			/*
			 * The cycle already started =>
			 * nothing todo
			 */
			continue;
		}

		/*
		 * Get the current retransmission timeout value.
		 *
		 * It may change over time, but fetching it once
		 * per 'pending break' cycled should be enough.
		 */
		status = smbXsrv_connection_get_rto_usecs(xconn,
							  &xconn->ack.rto_usecs);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}

		/*
		 * At the start of the cycle we reset the
		 * unacked_bytes counter (first to 0 and
		 * within smbXsrv_connection_get_acked_bytes()
		 * to the current value in the kernel
		 * send queue.
		 */
		xconn->ack.unacked_bytes = 0;
		status = smbXsrv_connection_get_acked_bytes(xconn, &acked_bytes);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}

		/*
		 * We setup a timer in order to check for
		 * acked bytes after one retransmission timeout.
		 *
		 * The code that sets up the send_queue.ack.timeout
		 * uses a multiple of the retransmission timeout.
		 */
		next_check = timeval_current_ofs_usec(xconn->ack.rto_usecs);
		xconn->ack.checker_subreq = tevent_wakeup_send(xconn,
							client->raw_ev_ctx,
							next_check);
		if (xconn->ack.checker_subreq == NULL) {
			return NT_STATUS_NO_MEMORY;
		}
		tevent_req_set_callback(xconn->ack.checker_subreq,
					smbXsrv_connection_ack_checker,
					xconn);
	}

	return NT_STATUS_OK;
}

void smbXsrv_connection_disconnect_transport(struct smbXsrv_connection *xconn,
					     NTSTATUS status)
{
	if (!NT_STATUS_IS_OK(xconn->transport.status)) {
		return;
	}

	xconn->transport.status = status;
	TALLOC_FREE(xconn->transport.fde);
	if (xconn->transport.sock != -1) {
		xconn->transport.sock = -1;
	}
	smbd_smb2_send_queue_ack_fail(&xconn->ack.queue, status);
	smbd_smb2_send_queue_ack_fail(&xconn->smb2.send_queue, status);
	xconn->smb2.send_queue_len = 0;
	DO_PROFILE_INC(disconnect);
}

size_t smbXsrv_client_valid_connections(struct smbXsrv_client *client)
{
	struct smbXsrv_connection *xconn = NULL;
	size_t num_ok = 0;

	for (xconn = client->connections; xconn != NULL; xconn = xconn->next) {
		if (NT_STATUS_IS_OK(xconn->transport.status)) {
			num_ok++;
		}
	}

	return num_ok;
}

struct smbXsrv_connection_shutdown_state {
	struct smbXsrv_connection *xconn;
};

static void smbXsrv_connection_shutdown_wait_done(struct tevent_req *subreq);

static struct tevent_req *smbXsrv_connection_shutdown_send(TALLOC_CTX *mem_ctx,
					struct tevent_context *ev,
					struct smbXsrv_connection *xconn)
{
	struct tevent_req *req = NULL;
	struct smbXsrv_connection_shutdown_state *state = NULL;
	struct tevent_req *subreq = NULL;
	size_t len = 0;
	struct smbd_smb2_request *preq = NULL;
	NTSTATUS status;

	/*
	 * The caller should have called
	 * smbXsrv_connection_disconnect_transport() before.
	 */
	SMB_ASSERT(!NT_STATUS_IS_OK(xconn->transport.status));
	SMB_ASSERT(xconn->transport.terminating);
	SMB_ASSERT(xconn->transport.shutdown_wait_queue == NULL);

	req = tevent_req_create(mem_ctx, &state,
				struct smbXsrv_connection_shutdown_state);
	if (req == NULL) {
		return NULL;
	}

	state->xconn = xconn;
	tevent_req_defer_callback(req, ev);

	xconn->transport.shutdown_wait_queue =
		tevent_queue_create(state, "smbXsrv_connection_shutdown_queue");
	if (tevent_req_nomem(xconn->transport.shutdown_wait_queue, req)) {
		return tevent_req_post(req, ev);
	}

	for (preq = xconn->smb2.requests; preq != NULL; preq = preq->next) {
		/*
		 * Now wait until the request is finished.
		 *
		 * We don't set a callback, as we just want to block the
		 * wait queue and the talloc_free() of the request will
		 * remove the item from the wait queue.
		 *
		 * Note that we don't cancel the requests here
		 * in order to keep the replay detection logic correct.
		 *
		 * However if we teardown the last channel of
		 * a connection, we'll call some logic via
		 * smbXsrv_session_disconnect_xconn()
		 * -> smbXsrv_session_disconnect_xconn_callback()
		 *   -> smbXsrv_session_remove_channel()
		 *     -> smb2srv_session_shutdown_send()
		 * will indeed cancel the request.
		 */
		subreq = tevent_queue_wait_send(preq, ev,
					xconn->transport.shutdown_wait_queue);
		if (tevent_req_nomem(subreq, req)) {
			return tevent_req_post(req, ev);
		}
	}

	/*
	 * This may attach sessions with num_channels == 0
	 * to xconn->transport.shutdown_wait_queue.
	 */
	status = smbXsrv_session_disconnect_xconn(xconn);
	if (tevent_req_nterror(req, status)) {
		return tevent_req_post(req, ev);
	}

	len = tevent_queue_length(xconn->transport.shutdown_wait_queue);
	if (len == 0) {
		tevent_req_done(req);
		return tevent_req_post(req, ev);
	}

	/*
	 * Now we add our own waiter to the end of the queue,
	 * this way we get notified when all pending requests are finished
	 * and send to the socket.
	 */
	subreq = tevent_queue_wait_send(state, ev, xconn->transport.shutdown_wait_queue);
	if (tevent_req_nomem(subreq, req)) {
		return tevent_req_post(req, ev);
	}
	tevent_req_set_callback(subreq, smbXsrv_connection_shutdown_wait_done, req);

	return req;
}

static void smbXsrv_connection_shutdown_wait_done(struct tevent_req *subreq)
{
	struct tevent_req *req =
		tevent_req_callback_data(subreq,
		struct tevent_req);
	struct smbXsrv_connection_shutdown_state *state =
		tevent_req_data(req,
		struct smbXsrv_connection_shutdown_state);
	struct smbXsrv_connection *xconn = state->xconn;

	tevent_queue_wait_recv(subreq);
	TALLOC_FREE(subreq);

	tevent_req_done(req);
	/*
	 * make sure the xconn pointer is still valid,
	 * it should as we used tevent_req_defer_callback()
	 */
	SMB_ASSERT(xconn->transport.terminating);
}

static NTSTATUS smbXsrv_connection_shutdown_recv(struct tevent_req *req)
{
	struct smbXsrv_connection_shutdown_state *state =
		tevent_req_data(req,
		struct smbXsrv_connection_shutdown_state);
	struct smbXsrv_connection *xconn = state->xconn;
	/*
	 * make sure the xconn pointer is still valid,
	 * it should as we used tevent_req_defer_callback()
	 */
	SMB_ASSERT(xconn->transport.terminating);
	return tevent_req_simple_recv_ntstatus(req);
}

struct smbd_server_connection_terminate_state {
	struct smbXsrv_connection *xconn;
	char *reason;
};

static void smbd_server_connection_terminate_done(struct tevent_req *subreq)
{
	struct smbd_server_connection_terminate_state *state =
		tevent_req_callback_data(
			subreq,
			struct smbd_server_connection_terminate_state);
	struct smbXsrv_connection *xconn = state->xconn;
	struct smbXsrv_client *client = xconn->client;
	const char *reason = state->reason;
	size_t num_ok;
	NTSTATUS status;

	status = smbXsrv_connection_shutdown_recv(subreq);
	TALLOC_FREE(subreq);
	if (!NT_STATUS_IS_OK(status)) {
		exit_server("smbXsrv_connection_shutdown_recv failed");
	}

	DLIST_REMOVE(client->connections, xconn);
	TALLOC_FREE(xconn);

	num_ok = smbXsrv_client_valid_connections(client);
	if (num_ok > 0) {
		return;
	}

	/*
	 * The last connection was disconnected
	 */
	exit_server_cleanly(reason);
}

void smbd_server_connection_terminate_ex(struct smbXsrv_connection *xconn,
					 const char *reason,
					 const char *location)
{
	struct smbd_server_connection_terminate_state *state = NULL;
	struct smbXsrv_client *client = xconn->client;
	struct tevent_req *subreq = NULL;
	size_t num_ok = 0;

	state = talloc_zero(xconn, struct smbd_server_connection_terminate_state);
	if (state == NULL) {
		exit_server("smbXsrv_connection_shutdown_send failed");
	}
	state->xconn = xconn;
	state->reason = talloc_strdup(NULL, reason);
	if (state->reason == NULL) {
		exit_server("talloc_strdup failed");
	}

	/*
	 * Make sure that no new request will be able to use this session.
	 *
	 * smbXsrv_connection_disconnect_transport() might be called already,
	 * but calling it again is a no-op.
	 */
	smbXsrv_connection_disconnect_transport(xconn,
					NT_STATUS_CONNECTION_DISCONNECTED);

	num_ok = smbXsrv_client_valid_connections(client);

	if (xconn->transport.terminating) {
		DBG_DEBUG("skip recursion conn[%s] num_ok[%zu] reason[%s] at %s\n",
			  smbXsrv_connection_dbg(xconn), num_ok,
			  reason, location);
		return;
	}
	xconn->transport.terminating = true;

	DBG_DEBUG("conn[%s] num_ok[%zu] reason[%s] at %s\n",
		  smbXsrv_connection_dbg(xconn), num_ok,
		  reason, location);

	if (xconn->has_cluster_movable_ip) {
		/*
		 * If the connection has a movable cluster public address
		 * we disconnect all client connections,
		 * as the public address might be moved to
		 * a different node.
		 *
		 * In future we may recheck which node currently
		 * holds this address, but for now we keep it simple.
		 */
		smbd_server_disconnect_client_ex(xconn->client,
						 reason,
						 location);
		return;
	}

	subreq = smbXsrv_connection_shutdown_send(client,
						  client->raw_ev_ctx,
						  xconn);
	if (subreq == NULL) {
		exit_server("smbXsrv_connection_shutdown_send failed");
	}
	tevent_req_set_callback(subreq,
				smbd_server_connection_terminate_done,
				state);
	return;
}

void smbd_server_disconnect_client_ex(struct smbXsrv_client *client,
				      const char *reason,
				      const char *location)
{
	size_t num_ok = 0;

	num_ok = smbXsrv_client_valid_connections(client);

	DBG_WARNING("client[%s] num_ok[%zu] reason[%s] at %s\n",
		    client->global->remote_address, num_ok,
		    reason, location);

	/*
	 * Something bad happened we need to disconnect all connections.
	 */
	exit_server_cleanly(reason);
}

static bool dup_smb2_vec4(TALLOC_CTX *ctx,
			struct iovec *outvec,
			const struct iovec *srcvec)
{
	const uint8_t *srctf;
	size_t srctf_len;
	const uint8_t *srchdr;
	size_t srchdr_len;
	const uint8_t *srcbody;
	size_t srcbody_len;
	const uint8_t *expected_srcbody;
	const uint8_t *srcdyn;
	size_t srcdyn_len;
	const uint8_t *expected_srcdyn;
	uint8_t *dsttf;
	uint8_t *dsthdr;
	uint8_t *dstbody;
	uint8_t *dstdyn;

	srctf  = (const uint8_t *)srcvec[SMBD_SMB2_TF_IOV_OFS].iov_base;
	srctf_len = srcvec[SMBD_SMB2_TF_IOV_OFS].iov_len;
	srchdr  = (const uint8_t *)srcvec[SMBD_SMB2_HDR_IOV_OFS].iov_base;
	srchdr_len = srcvec[SMBD_SMB2_HDR_IOV_OFS].iov_len;
	srcbody = (const uint8_t *)srcvec[SMBD_SMB2_BODY_IOV_OFS].iov_base;
	srcbody_len = srcvec[SMBD_SMB2_BODY_IOV_OFS].iov_len;
	expected_srcbody = srchdr + SMB2_HDR_BODY;
	srcdyn  = (const uint8_t *)srcvec[SMBD_SMB2_DYN_IOV_OFS].iov_base;
	srcdyn_len = srcvec[SMBD_SMB2_DYN_IOV_OFS].iov_len;
	expected_srcdyn = srcbody + 8;

	if ((srctf_len != SMB2_TF_HDR_SIZE) && (srctf_len != 0)) {
		return false;
	}

	if (srchdr_len != SMB2_HDR_BODY) {
		return false;
	}

	if (srctf_len == SMB2_TF_HDR_SIZE) {
		dsttf = talloc_memdup(ctx, srctf, SMB2_TF_HDR_SIZE);
		if (dsttf == NULL) {
			return false;
		}
	} else {
		dsttf = NULL;
	}
	outvec[SMBD_SMB2_TF_IOV_OFS].iov_base = (void *)dsttf;
	outvec[SMBD_SMB2_TF_IOV_OFS].iov_len = srctf_len;

	/* vec[SMBD_SMB2_HDR_IOV_OFS] is always boilerplate and must
	 * be allocated with size OUTVEC_ALLOC_SIZE. */

	dsthdr = talloc_memdup(ctx, srchdr, OUTVEC_ALLOC_SIZE);
	if (dsthdr == NULL) {
		return false;
	}
	outvec[SMBD_SMB2_HDR_IOV_OFS].iov_base = (void *)dsthdr;
	outvec[SMBD_SMB2_HDR_IOV_OFS].iov_len = SMB2_HDR_BODY;

	/*
	 * If this is a "standard" vec[SMBD_SMB2_BOFY_IOV_OFS] of length 8,
	 * pointing to srcvec[SMBD_SMB2_HDR_IOV_OFS].iov_base + SMB2_HDR_BODY,
	 * then duplicate this. Else use talloc_memdup().
	 */

	if ((srcbody == expected_srcbody) && (srcbody_len == 8)) {
		dstbody = dsthdr + SMB2_HDR_BODY;
	} else {
		dstbody = talloc_memdup(ctx, srcbody, srcbody_len);
		if (dstbody == NULL) {
			return false;
		}
	}
	outvec[SMBD_SMB2_BODY_IOV_OFS].iov_base = (void *)dstbody;
	outvec[SMBD_SMB2_BODY_IOV_OFS].iov_len = srcbody_len;

	/*
	 * If this is a "standard" vec[SMBD_SMB2_DYN_IOV_OFS] of length 1,
	 * pointing to
	 * srcvec[SMBD_SMB2_HDR_IOV_OFS].iov_base + 8
	 * then duplicate this. Else use talloc_memdup().
	 */

	if ((srcdyn == expected_srcdyn) && (srcdyn_len == 1)) {
		dstdyn = dsthdr + SMB2_HDR_BODY + 8;
	} else if (srcdyn == NULL) {
		dstdyn = NULL;
	} else {
		dstdyn = talloc_memdup(ctx, srcdyn, srcdyn_len);
		if (dstdyn == NULL) {
			return false;
		}
	}
	outvec[SMBD_SMB2_DYN_IOV_OFS].iov_base = (void *)dstdyn;
	outvec[SMBD_SMB2_DYN_IOV_OFS].iov_len = srcdyn_len;

	return true;
}

static struct smbd_smb2_request *dup_smb2_req(const struct smbd_smb2_request *req)
{
	struct smbd_smb2_request *newreq = NULL;
	struct iovec *outvec = NULL;
	int count = req->out.vector_count;
	int i;
	bool ok;

	newreq = smbd_smb2_request_allocate(req->xconn);
	if (!newreq) {
		return NULL;
	}

	newreq->session = req->session;
	newreq->do_encryption = req->do_encryption;
	newreq->do_signing = req->do_signing;
	newreq->current_idx = req->current_idx;

	outvec = talloc_zero_array(newreq, struct iovec, count);
	if (!outvec) {
		TALLOC_FREE(newreq);
		return NULL;
	}
	newreq->out.vector = outvec;
	newreq->out.vector_count = count;

	/* Setup the outvec's identically to req. */
	outvec[0].iov_base = newreq->out.nbt_hdr;
	outvec[0].iov_len = 4;
	memcpy(newreq->out.nbt_hdr, req->out.nbt_hdr, 4);

	/* Setup the vectors identically to the ones in req. */
	for (i = 1; i < count; i += SMBD_SMB2_NUM_IOV_PER_REQ) {
		if (!dup_smb2_vec4(outvec, &outvec[i], &req->out.vector[i])) {
			break;
		}
	}

	if (i < count) {
		/* Alloc failed. */
		TALLOC_FREE(newreq);
		return NULL;
	}

	ok = smb2_setup_nbt_length(newreq->out.vector,
				   newreq->out.vector_count);
	if (!ok) {
		TALLOC_FREE(newreq);
		return NULL;
	}

	return newreq;
}

static NTSTATUS smb2_send_async_interim_response(const struct smbd_smb2_request *req)
{
	struct smbXsrv_connection *xconn = req->xconn;
	int first_idx = 1;
	struct iovec *firsttf = NULL;
	struct iovec *outhdr_v = NULL;
	uint8_t *outhdr = NULL;
	struct smbd_smb2_request *nreq = NULL;
	NTSTATUS status;
	bool ok;

	/* Create a new smb2 request we'll use
	   for the interim return. */
	nreq = dup_smb2_req(req);
	if (!nreq) {
		return NT_STATUS_NO_MEMORY;
	}

	/* Lose the last X out vectors. They're the
	   ones we'll be using for the async reply. */
	nreq->out.vector_count -= SMBD_SMB2_NUM_IOV_PER_REQ;

	ok = smb2_setup_nbt_length(nreq->out.vector,
				   nreq->out.vector_count);
	if (!ok) {
		return NT_STATUS_INVALID_PARAMETER_MIX;
	}

	/* Step back to the previous reply. */
	nreq->current_idx -= SMBD_SMB2_NUM_IOV_PER_REQ;
	firsttf = SMBD_SMB2_IDX_TF_IOV(nreq,out,first_idx);
	outhdr_v = SMBD_SMB2_OUT_HDR_IOV(nreq);
	outhdr = SMBD_SMB2_OUT_HDR_PTR(nreq);
	/* And end the chain. */
	SIVAL(outhdr, SMB2_HDR_NEXT_COMMAND, 0);

	/* Calculate outgoing credits */
	smb2_calculate_credits(req, nreq);

	if (DEBUGLEVEL >= 10) {
		dbgtext("smb2_send_async_interim_response: nreq->current_idx = %u\n",
			(unsigned int)nreq->current_idx );
		dbgtext("smb2_send_async_interim_response: returning %u vectors\n",
			(unsigned int)nreq->out.vector_count );
		print_req_vectors(nreq);
	}

	/*
	 * As we have changed the header (SMB2_HDR_NEXT_COMMAND),
	 * we need to sign/encrypt here with the last/first key we remembered
	 */
	if (firsttf->iov_len == SMB2_TF_HDR_SIZE) {
		status = smb2_signing_encrypt_pdu(req->first_enc_key,
					firsttf,
					nreq->out.vector_count - first_idx);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}
	} else if (smb2_signing_key_valid(req->last_sign_key)) {
		status = smb2_signing_sign_pdu(req->last_sign_key,
					       outhdr_v,
					       SMBD_SMB2_NUM_IOV_PER_REQ - 1);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}
	}

	nreq->queue_entry.mem_ctx = nreq;
	nreq->queue_entry.vector = nreq->out.vector;
	nreq->queue_entry.count = nreq->out.vector_count;
	DLIST_ADD_END(xconn->smb2.send_queue, &nreq->queue_entry);
	xconn->smb2.send_queue_len++;

	status = smbd_smb2_flush_send_queue(xconn);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	return NT_STATUS_OK;
}

struct smbd_smb2_request_pending_state {
	struct smbd_smb2_send_queue queue_entry;
        uint8_t buf[NBT_HDR_SIZE + SMB2_TF_HDR_SIZE + SMB2_HDR_BODY + 0x08 + 1];
        struct iovec vector[1 + SMBD_SMB2_NUM_IOV_PER_REQ];
};

static void smbd_smb2_request_pending_timer(struct tevent_context *ev,
					    struct tevent_timer *te,
					    struct timeval current_time,
					    void *private_data);

NTSTATUS smbd_smb2_request_pending_queue(struct smbd_smb2_request *req,
					 struct tevent_req *subreq,
					 uint32_t defer_time)
{
	NTSTATUS status;
	struct timeval defer_endtime;
	uint8_t *outhdr = NULL;
	uint32_t flags;

	if (!tevent_req_is_in_progress(subreq)) {
		/*
		 * This is a performance optimization,
		 * it avoids one tevent_loop iteration,
		 * which means we avoid one
		 * talloc_stackframe_pool/talloc_free pair.
		 */
		tevent_req_notify_callback(subreq);
		return NT_STATUS_OK;
	}

	req->subreq = subreq;
	subreq = NULL;

	if (req->async_te) {
		/* We're already async. */
		return NT_STATUS_OK;
	}

	outhdr = SMBD_SMB2_OUT_HDR_PTR(req);
	flags = IVAL(outhdr, SMB2_HDR_FLAGS);
	if (flags & SMB2_HDR_FLAG_ASYNC) {
		/* We're already async. */
		return NT_STATUS_OK;
	}

	if (req->async_internal || defer_time == 0) {
		/*
		 * An SMB2 request implementation wants to handle the request
		 * asynchronously "internally" while keeping synchronous
		 * behaviour for the SMB2 request. This means we don't send an
		 * interim response and we can allow processing of compound SMB2
		 * requests (cf the subsequent check) for all cases.
		 */
		return NT_STATUS_OK;
	}

	if (req->in.vector_count > req->current_idx + SMBD_SMB2_NUM_IOV_PER_REQ) {
		/*
		 * We're trying to go async in a compound request
		 * chain. This is only allowed for opens that cause an
		 * oplock break or for the last operation in the
		 * chain, otherwise it is not allowed. See
		 * [MS-SMB2].pdf note <206> on Section 3.3.5.2.7.
		 */
		const uint8_t *inhdr = SMBD_SMB2_IN_HDR_PTR(req);

		if (SVAL(inhdr, SMB2_HDR_OPCODE) != SMB2_OP_CREATE) {
			/*
			 * Cancel the outstanding request.
			 */
			bool ok = tevent_req_cancel(req->subreq);
			if (ok) {
				return NT_STATUS_OK;
			}
			TALLOC_FREE(req->subreq);
			return smbd_smb2_request_error(req,
				NT_STATUS_INTERNAL_ERROR);
		}
	}

	if (DEBUGLEVEL >= 10) {
		dbgtext("smbd_smb2_request_pending_queue: req->current_idx = %u\n",
			(unsigned int)req->current_idx );
		print_req_vectors(req);
	}

	if (req->current_idx > 1) {
		/*
		 * We're going async in a compound
		 * chain after the first request has
		 * already been processed. Send an
		 * interim response containing the
		 * set of replies already generated.
		 */
		int idx = req->current_idx;

		status = smb2_send_async_interim_response(req);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}
		TALLOC_FREE(req->first_enc_key);

		req->current_idx = 1;

		/*
		 * Re-arrange the in.vectors to remove what
		 * we just sent.
		 */
		memmove(&req->in.vector[1],
			&req->in.vector[idx],
			sizeof(req->in.vector[0])*(req->in.vector_count - idx));
		req->in.vector_count = 1 + (req->in.vector_count - idx);

		/* Re-arrange the out.vectors to match. */
		memmove(&req->out.vector[1],
			&req->out.vector[idx],
			sizeof(req->out.vector[0])*(req->out.vector_count - idx));
		req->out.vector_count = 1 + (req->out.vector_count - idx);

		if (req->in.vector_count == 1 + SMBD_SMB2_NUM_IOV_PER_REQ) {
			/*
			 * We only have one remaining request as
			 * we've processed everything else.
			 * This is no longer a compound request.
			 */
			req->compound_related = false;
			outhdr = SMBD_SMB2_OUT_HDR_PTR(req);
			flags = (IVAL(outhdr, SMB2_HDR_FLAGS) & ~SMB2_HDR_FLAG_CHAINED);
			SIVAL(outhdr, SMB2_HDR_FLAGS, flags);
		}
	}
	TALLOC_FREE(req->last_sign_key);

	/*
	 * smbd_smb2_request_pending_timer() just send a packet
	 * to the client and doesn't need any impersonation.
	 * So we use req->xconn->client->raw_ev_ctx instead
	 * of req->ev_ctx here.
	 */
	defer_endtime = timeval_current_ofs_usec(defer_time);
	req->async_te = tevent_add_timer(req->xconn->client->raw_ev_ctx,
					 req, defer_endtime,
					 smbd_smb2_request_pending_timer,
					 req);
	if (req->async_te == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	return NT_STATUS_OK;
}

static
struct smb2_signing_key *smbd_smb2_signing_key(struct smbXsrv_session *session,
					       struct smbXsrv_connection *xconn,
					       bool *_has_channel)
{
	struct smbXsrv_channel_global0 *c = NULL;
	NTSTATUS status;
	struct smb2_signing_key *key = NULL;
	bool has_channel = false;

	status = smbXsrv_session_find_channel(session, xconn, &c);
	if (NT_STATUS_IS_OK(status)) {
		key = c->signing_key;
		has_channel = true;
	}

	if (!smb2_signing_key_valid(key)) {
		key = session->global->signing_key;
		has_channel = false;
	}

	if (_has_channel != NULL) {
		*_has_channel = has_channel;
	}

	return key;
}

static NTSTATUS smb2_get_new_nonce(struct smbXsrv_session *session,
				   uint64_t *new_nonce_high,
				   uint64_t *new_nonce_low)
{
	uint64_t nonce_high;
	uint64_t nonce_low;

	session->nonce_low += 1;
	if (session->nonce_low == 0) {
		session->nonce_low += 1;
		session->nonce_high += 1;
	}

	/*
	 * CCM and GCM algorithms must never have their
	 * nonce wrap, or the security of the whole
	 * communication and the keys is destroyed.
	 * We must drop the connection once we have
	 * transferred too much data.
	 *
	 * NOTE: We assume nonces greater than 8 bytes.
	 */
	if (session->nonce_high >= session->nonce_high_max) {
		return NT_STATUS_ENCRYPTION_FAILED;
	}

	nonce_high = session->nonce_high_random;
	nonce_high += session->nonce_high;
	nonce_low = session->nonce_low;

	*new_nonce_high = nonce_high;
	*new_nonce_low = nonce_low;
	return NT_STATUS_OK;
}

static void smbd_smb2_request_pending_timer(struct tevent_context *ev,
					    struct tevent_timer *te,
					    struct timeval current_time,
					    void *private_data)
{
	struct smbd_smb2_request *req =
		talloc_get_type_abort(private_data,
		struct smbd_smb2_request);
	struct smbXsrv_connection *xconn = req->xconn;
	struct smbd_smb2_request_pending_state *state = NULL;
	uint8_t *outhdr = NULL;
	const uint8_t *inhdr = NULL;
	uint8_t *tf = NULL;
	uint8_t *hdr = NULL;
	uint8_t *body = NULL;
	uint8_t *dyn = NULL;
	uint32_t flags = 0;
	uint64_t message_id = 0;
	uint64_t async_id = 0;
	NTSTATUS status;
	bool ok;

	TALLOC_FREE(req->async_te);

	/* Ensure our final reply matches the interim one. */
	inhdr = SMBD_SMB2_IN_HDR_PTR(req);
	outhdr = SMBD_SMB2_OUT_HDR_PTR(req);
	flags = IVAL(outhdr, SMB2_HDR_FLAGS);
	message_id = BVAL(outhdr, SMB2_HDR_MESSAGE_ID);

	async_id = message_id; /* keep it simple for now... */

	SIVAL(outhdr, SMB2_HDR_FLAGS, flags | SMB2_HDR_FLAG_ASYNC);
	SBVAL(outhdr, SMB2_HDR_ASYNC_ID, async_id);

	DEBUG(10,("smbd_smb2_request_pending_queue: opcode[%s] mid %llu "
		"going async\n",
		smb2_opcode_name(SVAL(inhdr, SMB2_HDR_OPCODE)),
		(unsigned long long)async_id ));

	/*
	 * What we send is identical to a smbd_smb2_request_error
	 * packet with an error status of STATUS_PENDING. Make use
	 * of this fact sometime when refactoring. JRA.
	 */

	state = talloc_zero(req->xconn, struct smbd_smb2_request_pending_state);
	if (state == NULL) {
		smbd_server_connection_terminate(xconn,
						 nt_errstr(NT_STATUS_NO_MEMORY));
		return;
	}

	tf = state->buf + NBT_HDR_SIZE;

	hdr = tf + SMB2_TF_HDR_SIZE;
	body = hdr + SMB2_HDR_BODY;
	dyn = body + 8;

	if (req->do_encryption) {
		uint64_t nonce_high = 0;
		uint64_t nonce_low = 0;
		uint64_t session_id = req->session->global->session_wire_id;

		status = smb2_get_new_nonce(req->session,
					    &nonce_high,
					    &nonce_low);
		if (!NT_STATUS_IS_OK(status)) {
			smbd_server_connection_terminate(xconn,
							 nt_errstr(status));
			return;
		}

		SIVAL(tf, SMB2_TF_PROTOCOL_ID, SMB2_TF_MAGIC);
		SBVAL(tf, SMB2_TF_NONCE+0, nonce_low);
		SBVAL(tf, SMB2_TF_NONCE+8, nonce_high);
		SBVAL(tf, SMB2_TF_SESSION_ID, session_id);
	}

	SIVAL(hdr, SMB2_HDR_PROTOCOL_ID, SMB2_MAGIC);
	SSVAL(hdr, SMB2_HDR_LENGTH, SMB2_HDR_BODY);
	SSVAL(hdr, SMB2_HDR_EPOCH, 0);
	SIVAL(hdr, SMB2_HDR_STATUS, NT_STATUS_V(NT_STATUS_PENDING));
	SSVAL(hdr, SMB2_HDR_OPCODE, SVAL(outhdr, SMB2_HDR_OPCODE));

	/*
	 * The STATUS_PENDING response has SMB2_HDR_FLAG_SIGNED
	 * clearedm, but echoes the signature field.
	 */
	flags &= ~SMB2_HDR_FLAG_SIGNED;
	SIVAL(hdr, SMB2_HDR_FLAGS, flags);
	SIVAL(hdr, SMB2_HDR_NEXT_COMMAND, 0);
	SBVAL(hdr, SMB2_HDR_MESSAGE_ID, message_id);
	SBVAL(hdr, SMB2_HDR_PID, async_id);
	SBVAL(hdr, SMB2_HDR_SESSION_ID,
		BVAL(outhdr, SMB2_HDR_SESSION_ID));
	memcpy(hdr+SMB2_HDR_SIGNATURE,
	       outhdr+SMB2_HDR_SIGNATURE, 16);

	SSVAL(body, 0x00, 0x08 + 1);

	SCVAL(body, 0x02, 0);
	SCVAL(body, 0x03, 0);
	SIVAL(body, 0x04, 0);
	/* Match W2K8R2... */
	SCVAL(dyn,  0x00, 0x21);

	state->vector[0].iov_base = (void *)state->buf;
	state->vector[0].iov_len = NBT_HDR_SIZE;

	if (req->do_encryption) {
		state->vector[1+SMBD_SMB2_TF_IOV_OFS].iov_base   = tf;
		state->vector[1+SMBD_SMB2_TF_IOV_OFS].iov_len    =
							SMB2_TF_HDR_SIZE;
	} else {
		state->vector[1+SMBD_SMB2_TF_IOV_OFS].iov_base   = NULL;
		state->vector[1+SMBD_SMB2_TF_IOV_OFS].iov_len    = 0;
	}

	state->vector[1+SMBD_SMB2_HDR_IOV_OFS].iov_base  = hdr;
	state->vector[1+SMBD_SMB2_HDR_IOV_OFS].iov_len   = SMB2_HDR_BODY;

	state->vector[1+SMBD_SMB2_BODY_IOV_OFS].iov_base = body;
	state->vector[1+SMBD_SMB2_BODY_IOV_OFS].iov_len  = 8;

	state->vector[1+SMBD_SMB2_DYN_IOV_OFS].iov_base  = dyn;
	state->vector[1+SMBD_SMB2_DYN_IOV_OFS].iov_len   = 1;

	ok = smb2_setup_nbt_length(state->vector,
				   1 + SMBD_SMB2_NUM_IOV_PER_REQ);
	if (!ok) {
		smbd_server_connection_terminate(
			xconn, nt_errstr(NT_STATUS_INTERNAL_ERROR));
		return;
	}

	/* Ensure we correctly go through crediting. Grant
	   the credits now, and zero credits on the final
	   response. */
	smb2_set_operation_credit(req->xconn,
			SMBD_SMB2_IN_HDR_IOV(req),
			&state->vector[1+SMBD_SMB2_HDR_IOV_OFS]);

	/*
	 * We add SMB2_HDR_FLAG_ASYNC after smb2_set_operation_credit()
	 * as it reacts on it
	 */
	SIVAL(hdr, SMB2_HDR_FLAGS, flags | SMB2_HDR_FLAG_ASYNC);

	if (DEBUGLVL(10)) {
		int i;

		for (i = 0; i < ARRAY_SIZE(state->vector); i++) {
			dbgtext("\tstate->vector[%u/%u].iov_len = %u\n",
				(unsigned int)i,
				(unsigned int)ARRAY_SIZE(state->vector),
				(unsigned int)state->vector[i].iov_len);
		}
	}

	if (req->do_encryption) {
		struct smbXsrv_session *x = req->session;
		struct smb2_signing_key *encryption_key = x->global->encryption_key;

		status = smb2_signing_encrypt_pdu(encryption_key,
					&state->vector[1+SMBD_SMB2_TF_IOV_OFS],
					SMBD_SMB2_NUM_IOV_PER_REQ);
		if (!NT_STATUS_IS_OK(status)) {
			smbd_server_connection_terminate(xconn,
						nt_errstr(status));
			return;
		}
	}

	state->queue_entry.mem_ctx = state;
	state->queue_entry.vector = state->vector;
	state->queue_entry.count = ARRAY_SIZE(state->vector);
	DLIST_ADD_END(xconn->smb2.send_queue, &state->queue_entry);
	xconn->smb2.send_queue_len++;

	status = smbd_smb2_flush_send_queue(xconn);
	if (!NT_STATUS_IS_OK(status)) {
		smbd_server_connection_terminate(xconn,
						 nt_errstr(status));
		return;
	}
}

static NTSTATUS smbd_smb2_request_process_cancel(struct smbd_smb2_request *req)
{
	struct smbXsrv_connection *xconn = req->xconn;
	struct smbd_smb2_request *cur;
	const uint8_t *inhdr;
	uint32_t flags;
	uint64_t search_message_id;
	uint64_t search_async_id;
	uint64_t found_id = 0;

	inhdr = SMBD_SMB2_IN_HDR_PTR(req);

	flags = IVAL(inhdr, SMB2_HDR_FLAGS);
	search_message_id = BVAL(inhdr, SMB2_HDR_MESSAGE_ID);
	search_async_id = BVAL(inhdr, SMB2_HDR_PID);

	/*
	 * We don't need the request anymore cancel requests never
	 * have a response.
	 *
	 * We defer the TALLOC_FREE(req) to the caller.
	 */
	DLIST_REMOVE(xconn->smb2.requests, req);

	for (cur = xconn->smb2.requests; cur; cur = cur->next) {
		const uint8_t *outhdr;
		uint64_t message_id;
		uint64_t async_id;

		if (cur->session != req->session) {
			continue;
		}

		if (cur->compound_related) {
			/*
			 * Never cancel anything in a compound request.
			 * Way too hard to deal with the result.
			 */
			continue;
		}

		outhdr = SMBD_SMB2_OUT_HDR_PTR(cur);

		message_id = BVAL(outhdr, SMB2_HDR_MESSAGE_ID);
		async_id = BVAL(outhdr, SMB2_HDR_PID);

		if (flags & SMB2_HDR_FLAG_ASYNC) {
			if (search_async_id == async_id) {
				found_id = async_id;
				break;
			}
		} else {
			if (search_message_id == message_id) {
				found_id = message_id;
				break;
			}
		}
	}

	if (cur && cur->subreq) {
		inhdr = SMBD_SMB2_IN_HDR_PTR(cur);
		DEBUG(10,("smbd_smb2_request_process_cancel: attempting to "
			"cancel opcode[%s] mid %llu\n",
			smb2_opcode_name(SVAL(inhdr, SMB2_HDR_OPCODE)),
                        (unsigned long long)found_id ));
		tevent_req_cancel(cur->subreq);
	}

	return NT_STATUS_OK;
}

/*************************************************************
 Ensure an incoming tid is a valid one for us to access.
 Change to the associated uid credentials and chdir to the
 valid tid directory.
*************************************************************/

static NTSTATUS smbd_smb2_request_check_tcon(struct smbd_smb2_request *req)
{
	const uint8_t *inhdr;
	uint32_t in_flags;
	uint32_t in_tid;
	struct smbXsrv_tcon *tcon;
	NTSTATUS status;
	NTTIME now = timeval_to_nttime(&req->request_time);

	req->tcon = NULL;

	inhdr = SMBD_SMB2_IN_HDR_PTR(req);

	in_flags = IVAL(inhdr, SMB2_HDR_FLAGS);
	in_tid = IVAL(inhdr, SMB2_HDR_TID);

	if (in_flags & SMB2_HDR_FLAG_CHAINED) {
		in_tid = req->last_tid;
	}

	req->last_tid = 0;

	status = smb2srv_tcon_lookup(req->session,
				     in_tid, now, &tcon);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	if (!change_to_user_and_service(
		    tcon->compat,
		    req->session->global->session_wire_id))
	{
		return NT_STATUS_ACCESS_DENIED;
	}

	req->tcon = tcon;
	req->last_tid = in_tid;

	return NT_STATUS_OK;
}

/*************************************************************
 Ensure an incoming session_id is a valid one for us to access.
*************************************************************/

static NTSTATUS smbd_smb2_request_check_session(struct smbd_smb2_request *req)
{
	const uint8_t *inhdr;
	uint32_t in_flags;
	uint16_t in_opcode;
	uint64_t in_session_id;
	struct smbXsrv_session *session = NULL;
	struct auth_session_info *session_info;
	NTSTATUS status;
	NTTIME now = timeval_to_nttime(&req->request_time);

	req->session = NULL;
	req->tcon = NULL;

	inhdr = SMBD_SMB2_IN_HDR_PTR(req);

	in_flags = IVAL(inhdr, SMB2_HDR_FLAGS);
	in_opcode = SVAL(inhdr, SMB2_HDR_OPCODE);
	in_session_id = BVAL(inhdr, SMB2_HDR_SESSION_ID);

	if (in_flags & SMB2_HDR_FLAG_CHAINED) {
		in_session_id = req->last_session_id;
	}

	req->last_session_id = 0;

	/* look an existing session up */
	switch (in_opcode) {
	case SMB2_OP_SESSSETUP:
		/*
		 * For a session bind request, we don't have the
		 * channel set up at this point yet, so we defer
		 * the verification that the connection belongs
		 * to the session to the session setup code, which
		 * can look at the session binding flags.
		 */
		status = smb2srv_session_lookup_client(req->xconn->client,
						       in_session_id, now,
						       &session);
		break;
	default:
		status = smb2srv_session_lookup_conn(req->xconn,
						     in_session_id, now,
						     &session);
		break;
	}
	if (session) {
		req->session = session;
		req->last_session_id = in_session_id;
	}
	if (NT_STATUS_EQUAL(status, NT_STATUS_USER_SESSION_DELETED)) {
		switch (in_opcode) {
		case SMB2_OP_SESSSETUP:
			status = smb2srv_session_lookup_global(req->xconn->client,
							       in_session_id,
							       req,
							       &session);
			if (NT_STATUS_IS_OK(status)) {
				/*
				 * We fallback to a session of
				 * another process in order to
				 * get the signing correct.
				 *
				 * We don't set req->last_session_id here.
				 */
				req->session = session;
			}
			break;
		default:
			break;
		}
	}
	if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED)) {
		switch (in_opcode) {
		case SMB2_OP_SESSSETUP:
			status = NT_STATUS_OK;
			break;
		case SMB2_OP_LOGOFF:
		case SMB2_OP_CLOSE:
		case SMB2_OP_LOCK:
		case SMB2_OP_CANCEL:
		case SMB2_OP_KEEPALIVE:
			/*
			 * [MS-SMB2] 3.3.5.2.9 Verifying the Session
			 * specifies that LOGOFF, CLOSE and (UN)LOCK
			 * should always be processed even on expired sessions.
			 *
			 * Also see the logic in
			 * smbd_smb2_request_process_lock().
			 *
			 * The smb2.session.expire2 test shows that
			 * CANCEL and KEEPALIVE/ECHO should also
			 * be processed.
			 */
			status = NT_STATUS_OK;
			break;
		default:
			break;
		}
	}
	if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
		switch (in_opcode) {
		case SMB2_OP_TCON:
		case SMB2_OP_CREATE:
		case SMB2_OP_GETINFO:
		case SMB2_OP_SETINFO:
			return NT_STATUS_INVALID_HANDLE;
		default:
			/*
			 * Notice the check for
			 * (session_info == NULL)
			 * below.
			 */
			status = NT_STATUS_OK;
			break;
		}
	}
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	session_info = session->global->auth_session_info;
	if (session_info == NULL) {
		return NT_STATUS_INVALID_HANDLE;
	}

	return NT_STATUS_OK;
}

NTSTATUS smbd_smb2_request_verify_creditcharge(struct smbd_smb2_request *req,
						uint32_t data_length)
{
	struct smbXsrv_connection *xconn = req->xconn;
	uint16_t needed_charge;
	uint16_t credit_charge = 1;
	const uint8_t *inhdr;

	inhdr = SMBD_SMB2_IN_HDR_PTR(req);

	if (xconn->smb2.credits.multicredit) {
		credit_charge = SVAL(inhdr, SMB2_HDR_CREDIT_CHARGE);
		credit_charge = MAX(credit_charge, 1);
	}

	needed_charge = (data_length - 1)/ 65536 + 1;

	DBGC_DEBUG(DBGC_SMB2_CREDITS,
		   "mid %llu, CreditCharge: %d, NeededCharge: %d\n",
		   (unsigned long long) BVAL(inhdr, SMB2_HDR_MESSAGE_ID),
		   credit_charge, needed_charge);

	if (needed_charge > credit_charge) {
		DBGC_WARNING(DBGC_SMB2_CREDITS,
			  "CreditCharge too low, given %d, needed %d\n",
			  credit_charge, needed_charge);
		return NT_STATUS_INVALID_PARAMETER;
	}

	return NT_STATUS_OK;
}

NTSTATUS smbd_smb2_request_verify_sizes(struct smbd_smb2_request *req,
					size_t expected_body_size)
{
	struct iovec *inhdr_v;
	const uint8_t *inhdr;
	uint16_t opcode;
	const uint8_t *inbody;
	size_t body_size;
	size_t min_dyn_size = expected_body_size & 0x00000001;
	int max_idx = req->in.vector_count - SMBD_SMB2_NUM_IOV_PER_REQ;

	/*
	 * The following should be checked already.
	 */
	if (req->in.vector_count < SMBD_SMB2_NUM_IOV_PER_REQ) {
		return NT_STATUS_INTERNAL_ERROR;
	}
	if (req->current_idx > max_idx) {
		return NT_STATUS_INTERNAL_ERROR;
	}

	inhdr_v = SMBD_SMB2_IN_HDR_IOV(req);
	if (inhdr_v->iov_len != SMB2_HDR_BODY) {
		return NT_STATUS_INTERNAL_ERROR;
	}
	if (SMBD_SMB2_IN_BODY_LEN(req) < 2) {
		return NT_STATUS_INTERNAL_ERROR;
	}

	inhdr = SMBD_SMB2_IN_HDR_PTR(req);
	opcode = SVAL(inhdr, SMB2_HDR_OPCODE);

	switch (opcode) {
	case SMB2_OP_IOCTL:
	case SMB2_OP_GETINFO:
	case SMB2_OP_WRITE:
		min_dyn_size = 0;
		break;
	}

	/*
	 * Now check the expected body size,
	 * where the last byte might be in the
	 * dynamic section..
	 */
	if (SMBD_SMB2_IN_BODY_LEN(req) != (expected_body_size & 0xFFFFFFFE)) {
		return NT_STATUS_INVALID_PARAMETER;
	}
	if (SMBD_SMB2_IN_DYN_LEN(req) < min_dyn_size) {
		return NT_STATUS_INVALID_PARAMETER;
	}

	inbody = SMBD_SMB2_IN_BODY_PTR(req);

	body_size = SVAL(inbody, 0x00);
	if (body_size != expected_body_size) {
		return NT_STATUS_INVALID_PARAMETER;
	}

	return NT_STATUS_OK;
}

/* Set a flag if not already set, return true if set */
bool smbXsrv_set_crypto_flag(uint8_t *flags, uint8_t flag)
{
	if ((flag == 0) || (*flags & flag)) {
		return false;
	}

	*flags |= flag;
	return true;
}

/*
 * Update encryption state tracking flags, this can be used to
 * determine whether whether the session or tcon is "encrypted".
 */
static void smb2srv_update_crypto_flags(struct smbd_smb2_request *req,
					uint16_t opcode,
					bool *update_session_globalp,
					bool *update_tcon_globalp)
{
	/* Default: assume unecrypted and unsigned */
	struct smbXsrv_session *session = req->session;
	struct smbXsrv_tcon *tcon = req->tcon;
	uint8_t encrypt_flag = SMBXSRV_PROCESSED_UNENCRYPTED_PACKET;
	uint8_t sign_flag = SMBXSRV_PROCESSED_UNSIGNED_PACKET;
	bool update_session = false;
	bool update_tcon = false;

	if (session->table == NULL) {
		/*
		 * sessions from smb2srv_session_lookup_global()
		 * have NT_STATUS_BAD_LOGON_SESSION_STATE
		 * and session->table == NULL.
		 *
		 * They only used to give the correct error
		 * status, we should not update any state.
		 */
		goto out;
	}

	if (req->was_encrypted && req->do_encryption) {
		encrypt_flag = SMBXSRV_PROCESSED_ENCRYPTED_PACKET;
		sign_flag = SMBXSRV_PROCESSED_SIGNED_PACKET;
	} else {
		/* Unencrypted packet, can be signed */
		if (req->do_signing) {
			sign_flag = SMBXSRV_PROCESSED_SIGNED_PACKET;
		}
	}

	update_session |= smbXsrv_set_crypto_flag(
		&session->global->encryption_flags, encrypt_flag);
	update_session |= smbXsrv_set_crypto_flag(
		&session->global->signing_flags, sign_flag);

	if (tcon) {
		update_tcon |= smbXsrv_set_crypto_flag(
			&tcon->global->encryption_flags, encrypt_flag);
		update_tcon |= smbXsrv_set_crypto_flag(
			&tcon->global->signing_flags, sign_flag);
	}

out:
	*update_session_globalp = update_session;
	*update_tcon_globalp = update_tcon;
	return;
}

static NTSTATUS smbd_smb2_request_dispatch_update_counts(
				struct smbd_smb2_request *req,
				bool modify_call)
{
	struct smbXsrv_connection *xconn = req->xconn;
	const uint8_t *inhdr;
	uint16_t channel_sequence;
	uint8_t generation_wrap = 0;
	uint32_t flags;
	int cmp;
	struct smbXsrv_open *op;
	bool update_open = false;
	NTSTATUS status = NT_STATUS_OK;

	SMB_ASSERT(!req->request_counters_updated);

	if (xconn->protocol < PROTOCOL_SMB3_00) {
		return NT_STATUS_OK;
	}

	if (req->compat_chain_fsp == NULL) {
		return NT_STATUS_OK;
	}

	op = req->compat_chain_fsp->op;
	if (op == NULL) {
		return NT_STATUS_OK;
	}

	inhdr = SMBD_SMB2_IN_HDR_PTR(req);
	flags = IVAL(inhdr, SMB2_HDR_FLAGS);
	channel_sequence = SVAL(inhdr, SMB2_HDR_CHANNEL_SEQUENCE);

	cmp = channel_sequence - op->global->channel_sequence;
	if (cmp < 0) {
		/*
		 * csn wrap. We need to watch out for long-running
		 * requests that are still sitting on a previously
		 * used csn. SMB2_OP_NOTIFY can take VERY long.
		 */
		generation_wrap += 1;
	}

	if (abs(cmp) > INT16_MAX) {
		/*
		 * [MS-SMB2] 3.3.5.2.10 - Verifying the Channel Sequence Number:
		 *
		 * If the channel sequence number of the request and the one
		 * known to the server are not equal, the channel sequence
		 * number and outstanding request counts are only updated
		 * "... if the unsigned difference using 16-bit arithmetic
		 * between ChannelSequence and Open.ChannelSequence is less than
		 * or equal to 0x7FFF ...".
		 * Otherwise, an error is returned for the modifying
		 * calls write, set_info, and ioctl.
		 *
		 * There are currently two issues with the description:
		 *
		 * * For the other calls, the document seems to imply
		 *   that processing continues without adapting the
		 *   counters (if the sequence numbers are not equal).
		 *
		 *   TODO: This needs clarification!
		 *
		 * * Also, the behaviour if the difference is larger
		 *   than 0x7FFF is not clear. The document seems to
		 *   imply that if such a difference is reached,
		 *   the server starts to ignore the counters or
		 *   in the case of the modifying calls, return errors.
		 *
		 *   TODO: This needs clarification!
		 *
		 * At this point Samba tries to be a little more
		 * clever than the description in the MS-SMB2 document
		 * by heuristically detecting and properly treating
		 * a 16 bit overflow of the client-submitted sequence
		 * number:
		 *
		 * If the stored channel sequence number is more than
		 * 0x7FFF larger than the one from the request, then
		 * the client-provided sequence number has likely
		 * overflown. We treat this case as valid instead
		 * of as failure.
		 *
		 * The MS-SMB2 behaviour would be setting cmp = -1.
		 */
		cmp *= -1;
	}

	if (flags & SMB2_HDR_FLAG_REPLAY_OPERATION) {
		if (cmp == 0 && op->pre_request_count == 0) {
			op->request_count += 1;
			req->request_counters_updated = true;
		} else if (cmp > 0 && op->pre_request_count == 0) {
			op->pre_request_count += op->request_count;
			op->request_count = 1;
			op->global->channel_sequence = channel_sequence;
			op->global->channel_generation += generation_wrap;
			update_open = true;
			req->request_counters_updated = true;
		} else if (modify_call) {
			return NT_STATUS_FILE_NOT_AVAILABLE;
		}
	} else {
		if (cmp == 0) {
			op->request_count += 1;
			req->request_counters_updated = true;
		} else if (cmp > 0) {
			op->pre_request_count += op->request_count;
			op->request_count = 1;
			op->global->channel_sequence = channel_sequence;
			op->global->channel_generation += generation_wrap;
			update_open = true;
			req->request_counters_updated = true;
		} else if (modify_call) {
			return NT_STATUS_FILE_NOT_AVAILABLE;
		}
	}
	req->channel_generation = op->global->channel_generation;

	if (update_open) {
		status = smbXsrv_open_update(op);
	}

	return status;
}

#ifdef WITH_PROFILE
static int smb2_request_to_snum(const struct smbd_smb2_request *req)
{
	return (req->tcon != NULL) && (req->tcon->compat != NULL)
		       ? SNUM(req->tcon->compat)
		       : GLOBAL_SECTION_SNUM;
}
#endif

NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
{
	struct smbXsrv_connection *xconn = req->xconn;
	const struct smbd_smb2_dispatch_table *call = NULL;
	const struct iovec *intf_v = SMBD_SMB2_IN_TF_IOV(req);
	const uint8_t *inhdr;
	uint16_t opcode;
	uint32_t flags;
	uint64_t mid;
	NTSTATUS status;
	NTSTATUS session_status;
	uint32_t allowed_flags;
	NTSTATUS return_value;
	struct smbXsrv_session *x = NULL;
	bool signing_required = false;
	bool encryption_desired = false;
	bool encryption_required = false;
	bool session_expired = false;

	inhdr = SMBD_SMB2_IN_HDR_PTR(req);

	DO_PROFILE_INC(request);

	SMB_ASSERT(!req->request_counters_updated);

	/* TODO: verify more things */

	flags = IVAL(inhdr, SMB2_HDR_FLAGS);
	opcode = SVAL(inhdr, SMB2_HDR_OPCODE);
	mid = BVAL(inhdr, SMB2_HDR_MESSAGE_ID);
	DBG_DEBUG("opcode[%s] mid = %"PRIu64"\n",
		  smb2_opcode_name(opcode),
		  mid);

	if (xconn->protocol >= PROTOCOL_SMB2_02) {
		/*
		 * once the protocol is negotiated
		 * SMB2_OP_NEGPROT is not allowed anymore
		 */
		if (opcode == SMB2_OP_NEGPROT) {
			/* drop the connection */
			return NT_STATUS_INVALID_PARAMETER;
		}
	} else {
		/*
		 * if the protocol is not negotiated yet
		 * only SMB2_OP_NEGPROT is allowed.
		 */
		if (opcode != SMB2_OP_NEGPROT) {
			/* drop the connection */
			return NT_STATUS_INVALID_PARAMETER;
		}
	}

	/*
	 * Check if the client provided a valid session id.
	 *
	 * As some command don't require a valid session id
	 * we defer the check of the session_status
	 */
	session_status = smbd_smb2_request_check_session(req);
	x = req->session;
	if (x != NULL) {
		signing_required = x->global->signing_flags & SMBXSRV_SIGNING_REQUIRED;
		encryption_desired = x->global->encryption_flags & SMBXSRV_ENCRYPTION_DESIRED;
		encryption_required = x->global->encryption_flags & SMBXSRV_ENCRYPTION_REQUIRED;
		session_expired =
			NT_STATUS_EQUAL(session_status,
					NT_STATUS_NETWORK_SESSION_EXPIRED);
	}

	req->async_internal = false;
	req->do_signing = false;

	if (opcode == SMB2_OP_SESSSETUP) {
		req->do_encryption = false;
	} else {
		req->do_encryption = encryption_desired;
	}

	req->was_encrypted = false;
	if (intf_v->iov_len == SMB2_TF_HDR_SIZE) {
		const uint8_t *intf = SMBD_SMB2_IN_TF_PTR(req);
		uint64_t tf_session_id = BVAL(intf, SMB2_TF_SESSION_ID);

		if (x != NULL && x->global->session_wire_id != tf_session_id) {
			DBG_ERR("invalid session_id "
				"in SMB2_HDR[%" PRIu64 "], SMB2_TF[%" PRIu64
				"]\n",
				x->global->session_wire_id,
				tf_session_id);
			/*
			 * TODO: windows allows this...
			 * should we drop the connection?
			 *
			 * For now we just return ACCESS_DENIED
			 * (Windows clients never trigger this)
			 * and wait for an update of [MS-SMB2].
			 */
			return smbd_smb2_request_error(req,
					NT_STATUS_ACCESS_DENIED);
		}

		req->was_encrypted = true;
		req->do_encryption = true;
	}

	if (encryption_required && !req->was_encrypted) {
		req->do_encryption = true;
		return smbd_smb2_request_error(req,
				NT_STATUS_ACCESS_DENIED);
	}

	call = smbd_smb2_call(opcode);
	if (call == NULL) {
		return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
	}

	allowed_flags = SMB2_HDR_FLAG_CHAINED |
			SMB2_HDR_FLAG_SIGNED |
			SMB2_HDR_FLAG_DFS;
	if (xconn->protocol >= PROTOCOL_SMB3_11) {
		allowed_flags |= SMB2_HDR_FLAG_PRIORITY_MASK;
	}
	if (opcode == SMB2_OP_NEGPROT) {
		if (lp_server_max_protocol() >= PROTOCOL_SMB3_11) {
			allowed_flags |= SMB2_HDR_FLAG_PRIORITY_MASK;
		}
	}
	if (opcode == SMB2_OP_CANCEL) {
		allowed_flags |= SMB2_HDR_FLAG_ASYNC;
	}
	if (xconn->protocol >= PROTOCOL_SMB3_00) {
		allowed_flags |= SMB2_HDR_FLAG_REPLAY_OPERATION;
	}
	if ((flags & ~allowed_flags) != 0) {
		return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
	}

	if (flags & SMB2_HDR_FLAG_CHAINED) {
		/*
		 * This check is mostly for giving the correct error code
		 * for compounded requests.
		 */
		if (!session_expired && !NT_STATUS_IS_OK(session_status)) {
			return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
		}
	} else {
		req->compat_chain_fsp = NULL;
	}

	if (req->was_encrypted) {
		signing_required = false;
	} else if (signing_required || (flags & SMB2_HDR_FLAG_SIGNED)) {
		struct smb2_signing_key *signing_key = NULL;
		bool has_channel = false;

		if (x == NULL) {
			/*
			 * MS-SMB2: 3.3.5.2.4 Verifying the Signature.
			 * If the SMB2 header of the SMB2 NEGOTIATE
			 * request has the SMB2_FLAGS_SIGNED bit set in the
			 * Flags field, the server MUST fail the request
			 * with STATUS_INVALID_PARAMETER.
			 *
			 * Microsoft test tool checks this.
			 */

			if ((opcode == SMB2_OP_NEGPROT) &&
					(flags & SMB2_HDR_FLAG_SIGNED)) {
				status = NT_STATUS_INVALID_PARAMETER;
			} else {
				status = NT_STATUS_USER_SESSION_DELETED;
			}
			return smbd_smb2_request_error(req, status);
		}

		signing_key = smbd_smb2_signing_key(x, xconn, &has_channel);

		/*
		 * If we have a signing key, we should
		 * sign the response
		 */
		if (smb2_signing_key_valid(signing_key) && opcode != SMB2_OP_CANCEL) {
			req->do_signing = true;
		}

		status = smb2_signing_check_pdu(signing_key,
						SMBD_SMB2_IN_HDR_IOV(req),
						SMBD_SMB2_NUM_IOV_PER_REQ - 1);
		if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) &&
		    opcode == SMB2_OP_SESSSETUP && !has_channel &&
		    NT_STATUS_IS_OK(session_status))
		{
			if (!NT_STATUS_EQUAL(x->status, NT_STATUS_BAD_LOGON_SESSION_STATE)) {
				struct smbXsrv_session *session = NULL;
				NTSTATUS error;

				error = smb2srv_session_lookup_global(req->xconn->client,
								      x->global->session_wire_id,
								      req,
								      &session);
				if (!NT_STATUS_IS_OK(error)) {
					return smbd_smb2_request_error(req, error);
				}

				/*
				 * We fallback to a session of
				 * another process in order to
				 * get the signing correct.
				 *
				 * We don't set req->last_session_id here.
				 */
				req->session = x = session;
			}
			goto skipped_signing;
		}
		if (!NT_STATUS_IS_OK(status)) {
			return smbd_smb2_request_error(req, status);
		}

		/*
		 * Now that we know the request was correctly signed
		 * we have to sign the response too.
		 */
		if (opcode != SMB2_OP_CANCEL) {
			req->do_signing = true;
		}

		if (!NT_STATUS_IS_OK(session_status)) {
			if (session_expired && opcode == SMB2_OP_CREATE) {
				req->compound_create_err = session_status;
			}
			return smbd_smb2_request_error(req, session_status);
		}
	}

	if (opcode == SMB2_OP_IOCTL) {
		/*
		 * Some special IOCTL calls don't require
		 * file, tcon nor session.
		 *
		 * They typically don't do any real action
		 * on behalf of the client.
		 *
		 * They are mainly used to alter the behavior
		 * of the connection for testing. So we can
		 * run as root and skip all file, tcon and session
		 * checks below.
		 */
		static const struct smbd_smb2_dispatch_table _root_ioctl_call = {
			.opcode = SMB2_OP_IOCTL,
			.as_root = true,
		};
		const uint8_t *body = SMBD_SMB2_IN_BODY_PTR(req);
		size_t body_size = SMBD_SMB2_IN_BODY_LEN(req);
		uint32_t in_ctl_code;
		size_t needed = 8;

		if (needed > body_size) {
			return smbd_smb2_request_error(req,
					NT_STATUS_INVALID_PARAMETER);
		}

		in_ctl_code = IVAL(body, 0x04);
		/*
		 * Only add trusted IOCTL codes here!
		 */
		switch (in_ctl_code) {
		case FSCTL_SMBTORTURE_FORCE_UNACKED_TIMEOUT:
			call = &_root_ioctl_call;
			break;
		case FSCTL_VALIDATE_NEGOTIATE_INFO:
			call = &_root_ioctl_call;
			break;
		case FSCTL_QUERY_NETWORK_INTERFACE_INFO:
			call = &_root_ioctl_call;
			break;
		}
	}

skipped_signing:

	if (flags & SMB2_HDR_FLAG_CHAINED) {
		if (!NT_STATUS_IS_OK(req->compound_create_err)) {
			return smbd_smb2_request_error(req,
					req->compound_create_err);
		}
		req->compound_related = true;
	}

	if (call->need_session) {
		if (!NT_STATUS_IS_OK(session_status)) {
			if (session_expired && opcode == SMB2_OP_CREATE) {
				req->compound_create_err = session_status;
			}
			return smbd_smb2_request_error(req, session_status);
		}
	}

	if (call->need_tcon) {
		SMB_ASSERT(call->need_session);

		/*
		 * This call needs to be run as user.
		 *
		 * smbd_smb2_request_check_tcon()
		 * calls change_to_user() on success.
		 * Which implies set_current_user_info()
		 * and chdir_current_service().
		 */
		status = smbd_smb2_request_check_tcon(req);
		if (!NT_STATUS_IS_OK(status)) {
			return smbd_smb2_request_error(req, status);
		}
		if (req->tcon->global->encryption_flags & SMBXSRV_ENCRYPTION_DESIRED) {
			encryption_desired = true;
		}
		if (req->tcon->global->encryption_flags & SMBXSRV_ENCRYPTION_REQUIRED) {
			encryption_required = true;
		}
		if (encryption_required && !req->was_encrypted) {
			req->do_encryption = true;
			return smbd_smb2_request_error(req,
				NT_STATUS_ACCESS_DENIED);
		} else if (encryption_desired) {
			req->do_encryption = true;
		}
	} else if (call->need_session) {
		struct auth_session_info *session_info = NULL;

		/*
		 * Unless we also have need_tcon (see above),
		 * we still need to call set_current_user_info().
		 */

		session_info = req->session->global->auth_session_info;
		if (session_info == NULL) {
			return NT_STATUS_INVALID_HANDLE;
		}

		set_current_user_info(session_info->unix_info->sanitized_username,
				      session_info->unix_info->unix_name,
				      session_info->info->domain_name);
	}

	if (req->session) {
		bool update_session_global = false;
		bool update_tcon_global = false;

		smb2srv_update_crypto_flags(req, opcode,
					    &update_session_global,
					    &update_tcon_global);

		if (update_session_global) {
			status = smbXsrv_session_update(x);
			if (!NT_STATUS_IS_OK(status)) {
				return smbd_smb2_request_error(req, status);
			}
		}
		if (update_tcon_global) {
			status = smbXsrv_tcon_update(req->tcon);
			if (!NT_STATUS_IS_OK(status)) {
				return smbd_smb2_request_error(req, status);
			}
		}
	}

	if (call->fileid_ofs != 0) {
		size_t needed = call->fileid_ofs + 16;
		const uint8_t *body = SMBD_SMB2_IN_BODY_PTR(req);
		size_t body_size = SMBD_SMB2_IN_BODY_LEN(req);
		uint64_t file_id_persistent;
		uint64_t file_id_volatile;
		struct files_struct *fsp;

		SMB_ASSERT(call->need_tcon);

		if (needed > body_size) {
			return smbd_smb2_request_error(req,
					NT_STATUS_INVALID_PARAMETER);
		}

		file_id_persistent	= BVAL(body, call->fileid_ofs + 0);
		file_id_volatile	= BVAL(body, call->fileid_ofs + 8);

		fsp = file_fsp_smb2(req, file_id_persistent, file_id_volatile);
		if (fsp == NULL) {
			if (req->compound_related &&
			    !NT_STATUS_IS_OK(req->compound_create_err))
			{
				return smbd_smb2_request_error(req,
						req->compound_create_err);
			}
			/*
			 * smbd_smb2_request_process_ioctl()
			 * has more checks in order to return more
			 * detailed error codes...
			 */
			if (opcode != SMB2_OP_IOCTL) {
				return smbd_smb2_request_error(req,
						NT_STATUS_FILE_CLOSED);
			}
		} else {
			if (fsp->fsp_flags.encryption_required && !req->was_encrypted) {
				return smbd_smb2_request_error(req,
						NT_STATUS_ACCESS_DENIED);
			}
		}
	}

	status = smbd_smb2_request_dispatch_update_counts(req, call->modify);
	if (!NT_STATUS_IS_OK(status)) {
		return smbd_smb2_request_error(req, status);
	}

	if (call->as_root) {
		SMB_ASSERT(call->fileid_ofs == 0);
		/* This call needs to be run as root */
		change_to_root_user();
	} else if (opcode != SMB2_OP_KEEPALIVE) {
		SMB_ASSERT(call->need_tcon);
	}

#define _INBYTES(_r) \
	iov_buflen(SMBD_SMB2_IN_HDR_IOV(_r), SMBD_SMB2_NUM_IOV_PER_REQ-1)

	switch (opcode) {
	case SMB2_OP_NEGPROT:
		SMBPROFILE_IOBYTES_ASYNC_START_X(smb2_request_to_snum(req),
						 smb2_negprot,
						 req->profile,
						 req->profile_x,
						 _INBYTES(req));
		return_value = smbd_smb2_request_process_negprot(req);
		break;

	case SMB2_OP_SESSSETUP:
		SMBPROFILE_IOBYTES_ASYNC_START_X(smb2_request_to_snum(req),
						 smb2_sesssetup,
						 req->profile,
						 req->profile_x,
						 _INBYTES(req));
		return_value = smbd_smb2_request_process_sesssetup(req);
		break;

	case SMB2_OP_LOGOFF:
		SMBPROFILE_IOBYTES_ASYNC_START_X(smb2_request_to_snum(req),
						 smb2_logoff,
						 req->profile,
						 req->profile_x,
						 _INBYTES(req));
		return_value = smbd_smb2_request_process_logoff(req);
		break;

	case SMB2_OP_TCON:
		SMBPROFILE_IOBYTES_ASYNC_START_X(smb2_request_to_snum(req),
						 smb2_tcon,
						 req->profile,
						 req->profile_x,
						 _INBYTES(req));
		return_value = smbd_smb2_request_process_tcon(req);
		break;

	case SMB2_OP_TDIS:
		SMBPROFILE_IOBYTES_ASYNC_START_X(smb2_request_to_snum(req),
						 smb2_tdis,
						 req->profile,
						 req->profile_x,
						 _INBYTES(req));
		return_value = smbd_smb2_request_process_tdis(req);
		break;

	case SMB2_OP_CREATE:
		if (req->subreq == NULL) {
			SMBPROFILE_IOBYTES_ASYNC_START_X(smb2_request_to_snum(
								 req),
							 smb2_create,
							 req->profile,
							 req->profile_x,
							 _INBYTES(req));
		} else {
			SMBPROFILE_IOBYTES_ASYNC_SET_BUSY_X(req->profile,
							    req->profile_x);
		}
		return_value = smbd_smb2_request_process_create(req);
		break;

	case SMB2_OP_CLOSE:
		SMBPROFILE_IOBYTES_ASYNC_START_X(smb2_request_to_snum(req),
						 smb2_close,
						 req->profile,
						 req->profile_x,
						 _INBYTES(req));
		return_value = smbd_smb2_request_process_close(req);
		break;

	case SMB2_OP_FLUSH:
		SMBPROFILE_IOBYTES_ASYNC_START_X(smb2_request_to_snum(req),
						 smb2_flush,
						 req->profile,
						 req->profile_x,
						 _INBYTES(req));
		return_value = smbd_smb2_request_process_flush(req);
		break;

	case SMB2_OP_READ:
		SMBPROFILE_IOBYTES_ASYNC_START_X(smb2_request_to_snum(req),
						 smb2_read,
						 req->profile,
						 req->profile_x,
						 _INBYTES(req));
		return_value = smbd_smb2_request_process_read(req);
		break;

	case SMB2_OP_WRITE:
		SMBPROFILE_IOBYTES_ASYNC_START_X(smb2_request_to_snum(req),
						 smb2_write,
						 req->profile,
						 req->profile_x,
						 _INBYTES(req));
		return_value = smbd_smb2_request_process_write(req);
		break;

	case SMB2_OP_LOCK:
		SMBPROFILE_IOBYTES_ASYNC_START_X(smb2_request_to_snum(req),
						 smb2_lock,
						 req->profile,
						 req->profile_x,
						 _INBYTES(req));
		return_value = smbd_smb2_request_process_lock(req);
		break;

	case SMB2_OP_IOCTL:
		SMBPROFILE_IOBYTES_ASYNC_START_X(smb2_request_to_snum(req),
						 smb2_ioctl,
						 req->profile,
						 req->profile_x,
						 _INBYTES(req));
		return_value = smbd_smb2_request_process_ioctl(req);
		break;

	case SMB2_OP_CANCEL:
		SMBPROFILE_IOBYTES_ASYNC_START_X(smb2_request_to_snum(req),
						 smb2_cancel,
						 req->profile,
						 req->profile_x,
						 _INBYTES(req));
		return_value = smbd_smb2_request_process_cancel(req);
		SMBPROFILE_IOBYTES_ASYNC_END_X(req->profile,
					       req->profile_x,
					       0,
					       SMB2_OP_CANCEL,
					       NT_STATUS_OK);

		/*
		 * We don't need the request anymore cancel requests never
		 * have a response.
		 *
		 * smbd_smb2_request_process_cancel() already called
		 * DLIST_REMOVE(xconn->smb2.requests, req);
		 */
		TALLOC_FREE(req);

		break;

	case SMB2_OP_KEEPALIVE:
		SMBPROFILE_IOBYTES_ASYNC_START_X(smb2_request_to_snum(req),
						 smb2_keepalive,
						 req->profile,
						 req->profile_x,
						 _INBYTES(req));
		return_value = smbd_smb2_request_process_keepalive(req);
		break;

	case SMB2_OP_QUERY_DIRECTORY:
		SMBPROFILE_IOBYTES_ASYNC_START_X(smb2_request_to_snum(req),
						 smb2_find,
						 req->profile,
						 req->profile_x,
						 _INBYTES(req));
		return_value = smbd_smb2_request_process_query_directory(req);
		break;

	case SMB2_OP_NOTIFY:
		SMBPROFILE_IOBYTES_ASYNC_START_X(smb2_request_to_snum(req),
						 smb2_notify,
						 req->profile,
						 req->profile_x,
						 _INBYTES(req));
		return_value = smbd_smb2_request_process_notify(req);
		break;

	case SMB2_OP_GETINFO:
		SMBPROFILE_IOBYTES_ASYNC_START_X(smb2_request_to_snum(req),
						 smb2_getinfo,
						 req->profile,
						 req->profile_x,
						 _INBYTES(req));
		return_value = smbd_smb2_request_process_getinfo(req);
		break;

	case SMB2_OP_SETINFO:
		SMBPROFILE_IOBYTES_ASYNC_START_X(smb2_request_to_snum(req),
						 smb2_setinfo,
						 req->profile,
						 req->profile_x,
						 _INBYTES(req));
		return_value = smbd_smb2_request_process_setinfo(req);
		break;

	case SMB2_OP_BREAK:
		SMBPROFILE_IOBYTES_ASYNC_START_X(smb2_request_to_snum(req),
						 smb2_break,
						 req->profile,
						 req->profile_x,
						 _INBYTES(req));
		return_value = smbd_smb2_request_process_break(req);
		break;

	default:
		return_value = smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
		break;
	}
	return return_value;
}

static void smbd_smb2_request_reply_update_counts(struct smbd_smb2_request *req)
{
	struct smbXsrv_connection *xconn = req->xconn;
	const uint8_t *inhdr;
	uint16_t channel_sequence;
	struct smbXsrv_open *op;

	if (!req->request_counters_updated) {
		return;
	}

	req->request_counters_updated = false;

	if (xconn->protocol < PROTOCOL_SMB3_00) {
		return;
	}

	if (req->compat_chain_fsp == NULL) {
		return;
	}

	op = req->compat_chain_fsp->op;
	if (op == NULL) {
		return;
	}

	inhdr = SMBD_SMB2_IN_HDR_PTR(req);
	channel_sequence = SVAL(inhdr, SMB2_HDR_CHANNEL_SEQUENCE);

	if ((op->global->channel_sequence == channel_sequence) &&
	    (op->global->channel_generation == req->channel_generation)) {
		SMB_ASSERT(op->request_count > 0);
		op->request_count -= 1;
	} else {
		SMB_ASSERT(op->pre_request_count > 0);
		op->pre_request_count -= 1;
	}
}

static NTSTATUS smbd_smb2_request_reply(struct smbd_smb2_request *req)
{
	struct smbXsrv_connection *xconn = req->xconn;
	int first_idx = 1;
	struct iovec *firsttf = SMBD_SMB2_IDX_TF_IOV(req,out,first_idx);
	struct iovec *outhdr = SMBD_SMB2_OUT_HDR_IOV(req);
	struct iovec *outdyn = SMBD_SMB2_OUT_DYN_IOV(req);
	NTSTATUS status;
	bool ok;

	req->subreq = NULL;
	TALLOC_FREE(req->async_te);

	/* MS-SMB2: 3.3.4.1 Sending Any Outgoing Message */
	smbd_smb2_request_reply_update_counts(req);

	if (req->do_encryption &&
	    (firsttf->iov_len == 0) &&
	    (!smb2_signing_key_valid(req->first_enc_key)) &&
	    (req->session != NULL) &&
	    smb2_signing_key_valid(req->session->global->encryption_key))
	{
		struct smb2_signing_key *encryption_key =
			req->session->global->encryption_key;
		uint8_t *tf;
		uint64_t session_id = req->session->global->session_wire_id;
		uint64_t nonce_high;
		uint64_t nonce_low;

		status = smb2_get_new_nonce(req->session,
					    &nonce_high,
					    &nonce_low);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}

		/*
		 * We need to place the SMB2_TRANSFORM header before the
		 * first SMB2 header
		 */

		/*
		 * we need to remember the encryption key
		 * and defer the signing/encryption until
		 * we are sure that we do not change
		 * the header again.
		 */
		status = smb2_signing_key_copy(req,
					       encryption_key,
					       &req->first_enc_key);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}

		tf = talloc_zero_array(req, uint8_t,
				       SMB2_TF_HDR_SIZE);
		if (tf == NULL) {
			return NT_STATUS_NO_MEMORY;
		}

		SIVAL(tf, SMB2_TF_PROTOCOL_ID, SMB2_TF_MAGIC);
		SBVAL(tf, SMB2_TF_NONCE+0, nonce_low);
		SBVAL(tf, SMB2_TF_NONCE+8, nonce_high);
		SBVAL(tf, SMB2_TF_SESSION_ID, session_id);

		firsttf->iov_base = (void *)tf;
		firsttf->iov_len = SMB2_TF_HDR_SIZE;
	}

	if ((req->current_idx > SMBD_SMB2_NUM_IOV_PER_REQ) &&
	    (smb2_signing_key_valid(req->last_sign_key)) &&
	    (firsttf->iov_len == 0))
	{
		int last_idx = req->current_idx - SMBD_SMB2_NUM_IOV_PER_REQ;
		struct iovec *lasthdr = SMBD_SMB2_IDX_HDR_IOV(req,out,last_idx);

		/*
		 * As we are sure the header of the last request in the
		 * compound chain will not change, we can to sign here
		 * with the last signing key we remembered.
		 */
		status = smb2_signing_sign_pdu(req->last_sign_key,
					       lasthdr,
					       SMBD_SMB2_NUM_IOV_PER_REQ - 1);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}
	}
	TALLOC_FREE(req->last_sign_key);

	SMBPROFILE_IOBYTES_ASYNC_END_X(
		req->profile,
		req->profile_x,
		iov_buflen(outhdr, SMBD_SMB2_NUM_IOV_PER_REQ - 1),
		PULL_LE_U16(outhdr->iov_base, SMB2_HDR_OPCODE),
		NT_STATUS(IVAL(outhdr->iov_base, SMB2_HDR_STATUS)));

	req->current_idx += SMBD_SMB2_NUM_IOV_PER_REQ;

	if (req->current_idx < req->out.vector_count) {
		/*
		 * We must process the remaining compound
		 * SMB2 requests before any new incoming SMB2
		 * requests. This is because incoming SMB2
		 * requests may include a cancel for a
		 * compound request we haven't processed
		 * yet.
		 */
		struct tevent_immediate *im = tevent_create_immediate(req);
		if (!im) {
			return NT_STATUS_NO_MEMORY;
		}

		if (req->do_signing && firsttf->iov_len == 0) {
			struct smbXsrv_session *x = req->session;
			struct smb2_signing_key *signing_key =
				smbd_smb2_signing_key(x, xconn, NULL);

			/*
			 * we need to remember the signing key
			 * and defer the signing until
			 * we are sure that we do not change
			 * the header again.
			 */
			status = smb2_signing_key_copy(req,
						       signing_key,
						       &req->last_sign_key);
			if (!NT_STATUS_IS_OK(status)) {
				return status;
			}
		}

		/*
		 * smbd_smb2_request_dispatch() will redo the impersonation.
		 * So we use req->xconn->client->raw_ev_ctx instead
		 * of req->ev_ctx here.
		 */
		tevent_schedule_immediate(im,
					req->xconn->client->raw_ev_ctx,
					smbd_smb2_request_dispatch_immediate,
					req);
		return NT_STATUS_OK;
	}

	if (req->compound_related) {
		req->compound_related = false;
	}

	ok = smb2_setup_nbt_length(req->out.vector, req->out.vector_count);
	if (!ok) {
		return NT_STATUS_INVALID_PARAMETER_MIX;
	}

	/* Set credit for these operations (zero credits if this
	   is a final reply for an async operation). */
	smb2_calculate_credits(req, req);

	/*
	 * now check if we need to sign the current response
	 */
	if (firsttf->iov_len == SMB2_TF_HDR_SIZE) {
		status = smb2_signing_encrypt_pdu(req->first_enc_key,
					firsttf,
					req->out.vector_count - first_idx);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}
	} else if (req->do_signing) {
		struct smbXsrv_session *x = req->session;
		struct smb2_signing_key *signing_key =
			smbd_smb2_signing_key(x, xconn, NULL);

		status = smb2_signing_sign_pdu(signing_key,
					       outhdr,
					       SMBD_SMB2_NUM_IOV_PER_REQ - 1);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}
	}
	TALLOC_FREE(req->first_enc_key);

	if (req->preauth != NULL) {
		gnutls_hash_hd_t hash_hnd = NULL;
		size_t i;
		int rc;

		rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_SHA512);
		if (rc < 0) {
			return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
		}
		rc = gnutls_hash(hash_hnd,
			    req->preauth->sha512_value,
			    sizeof(req->preauth->sha512_value));
		if (rc < 0) {
			gnutls_hash_deinit(hash_hnd, NULL);
			return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
		}
		for (i = 1; i < req->in.vector_count; i++) {
			rc = gnutls_hash(hash_hnd,
					 req->in.vector[i].iov_base,
					 req->in.vector[i].iov_len);
			if (rc < 0) {
				gnutls_hash_deinit(hash_hnd, NULL);
				return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
			}
		}
		gnutls_hash_output(hash_hnd, req->preauth->sha512_value);

		rc = gnutls_hash(hash_hnd,
				 req->preauth->sha512_value,
				 sizeof(req->preauth->sha512_value));
		if (rc < 0) {
			gnutls_hash_deinit(hash_hnd, NULL);
			return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
		}
		for (i = 1; i < req->out.vector_count; i++) {
			rc = gnutls_hash(hash_hnd,
					 req->out.vector[i].iov_base,
					 req->out.vector[i].iov_len);
			if (rc < 0) {
				gnutls_hash_deinit(hash_hnd, NULL);
				return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
			}
		}

		gnutls_hash_deinit(hash_hnd, req->preauth->sha512_value);

		req->preauth = NULL;
	}

	/* I am a sick, sick man... :-). Sendfile hack ... JRA. */
	if (req->out.vector_count < (2*SMBD_SMB2_NUM_IOV_PER_REQ) &&
	    outdyn->iov_base == NULL && outdyn->iov_len != 0) {
		/* Dynamic part is NULL. Chop it off,
		   We're going to send it via sendfile. */
		req->out.vector_count -= 1;
	}

	/*
	 * We're done with this request -
	 * move it off the "being processed" queue.
	 */
	DLIST_REMOVE(xconn->smb2.requests, req);

	req->queue_entry.mem_ctx = req;
	req->queue_entry.vector = req->out.vector;
	req->queue_entry.count = req->out.vector_count;
	DLIST_ADD_END(xconn->smb2.send_queue, &req->queue_entry);
	xconn->smb2.send_queue_len++;

	status = smbd_smb2_flush_send_queue(xconn);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	return NT_STATUS_OK;
}

static NTSTATUS smbd_smb2_request_next_incoming(struct smbXsrv_connection *xconn);

void smbd_smb2_request_dispatch_immediate(struct tevent_context *ctx,
					struct tevent_immediate *im,
					void *private_data)
{
	struct smbd_smb2_request *req = talloc_get_type_abort(private_data,
					struct smbd_smb2_request);
	struct smbXsrv_connection *xconn = req->xconn;
	NTSTATUS status;

	TALLOC_FREE(im);

	if (DEBUGLEVEL >= 10) {
		DBG_DEBUG("idx[%d] of %d vectors\n",
			  req->current_idx, req->in.vector_count);
		print_req_vectors(req);
	}

	status = smbd_smb2_request_dispatch(req);
	if (!NT_STATUS_IS_OK(status)) {
		smbd_server_connection_terminate(xconn, nt_errstr(status));
		return;
	}

	status = smbd_smb2_request_next_incoming(xconn);
	if (!NT_STATUS_IS_OK(status)) {
		smbd_server_connection_terminate(xconn, nt_errstr(status));
		return;
	}
}

NTSTATUS smbd_smb2_request_done_ex(struct smbd_smb2_request *req,
				   NTSTATUS status,
				   DATA_BLOB body, DATA_BLOB *dyn,
				   const char *location)
{
	uint8_t *outhdr;
	struct iovec *outbody_v;
	struct iovec *outdyn_v;
	uint32_t next_command_ofs;
	uint64_t mid;

	outhdr = SMBD_SMB2_OUT_HDR_PTR(req);
	mid = BVAL(outhdr, SMB2_HDR_MESSAGE_ID);

	DBG_DEBUG("mid [%"PRIu64"] idx[%d] status[%s] "
		  "body[%u] dyn[%s:%u] at %s\n",
		  mid,
		  req->current_idx,
		  nt_errstr(status),
		  (unsigned int)body.length,
		  dyn ? "yes" : "no",
		  (unsigned int)(dyn ? dyn->length : 0),
		  location);

	if (body.length < 2) {
		return smbd_smb2_request_error(req, NT_STATUS_INTERNAL_ERROR);
	}

	if ((body.length % 2) != 0) {
		return smbd_smb2_request_error(req, NT_STATUS_INTERNAL_ERROR);
	}

	outbody_v = SMBD_SMB2_OUT_BODY_IOV(req);
	outdyn_v = SMBD_SMB2_OUT_DYN_IOV(req);

	next_command_ofs = IVAL(outhdr, SMB2_HDR_NEXT_COMMAND);
	SIVAL(outhdr, SMB2_HDR_STATUS, NT_STATUS_V(status));

	outbody_v->iov_base = (void *)body.data;
	outbody_v->iov_len = body.length;

	if (dyn) {
		outdyn_v->iov_base = (void *)dyn->data;
		outdyn_v->iov_len = dyn->length;
	} else {
		outdyn_v->iov_base = NULL;
		outdyn_v->iov_len = 0;
	}

	/*
	 * See if we need to recalculate the offset to the next response
	 *
	 * Note that all responses may require padding (including the very last
	 * one).
	 */
	if (req->out.vector_count >= (2 * SMBD_SMB2_NUM_IOV_PER_REQ)) {
		next_command_ofs  = SMB2_HDR_BODY;
		next_command_ofs += SMBD_SMB2_OUT_BODY_LEN(req);
		next_command_ofs += SMBD_SMB2_OUT_DYN_LEN(req);
	}

	if ((next_command_ofs % 8) != 0) {
		size_t pad_size = 8 - (next_command_ofs % 8);
		if (SMBD_SMB2_OUT_DYN_LEN(req) == 0) {
			/*
			 * if the dyn buffer is empty
			 * we can use it to add padding
			 */
			uint8_t *pad;

			pad = talloc_zero_array(req,
						uint8_t, pad_size);
			if (pad == NULL) {
				return smbd_smb2_request_error(req,
						NT_STATUS_NO_MEMORY);
			}

			outdyn_v->iov_base = (void *)pad;
			outdyn_v->iov_len = pad_size;
		} else {
			/*
			 * For now we copy the dynamic buffer
			 * and add the padding to the new buffer
			 */
			size_t old_size;
			uint8_t *old_dyn;
			size_t new_size;
			uint8_t *new_dyn;

			old_size = SMBD_SMB2_OUT_DYN_LEN(req);
			old_dyn = SMBD_SMB2_OUT_DYN_PTR(req);

			new_size = old_size + pad_size;
			new_dyn = talloc_zero_array(req,
					       uint8_t, new_size);
			if (new_dyn == NULL) {
				return smbd_smb2_request_error(req,
						NT_STATUS_NO_MEMORY);
			}

			memcpy(new_dyn, old_dyn, old_size);
			memset(new_dyn + old_size, 0, pad_size);

			outdyn_v->iov_base = (void *)new_dyn;
			outdyn_v->iov_len = new_size;
		}
		next_command_ofs += pad_size;
	}

	if ((req->current_idx + SMBD_SMB2_NUM_IOV_PER_REQ) >= req->out.vector_count) {
		SIVAL(outhdr, SMB2_HDR_NEXT_COMMAND, 0);
	} else {
		SIVAL(outhdr, SMB2_HDR_NEXT_COMMAND, next_command_ofs);
	}
	return smbd_smb2_request_reply(req);
}

NTSTATUS smbd_smb2_request_error_ex(struct smbd_smb2_request *req,
				    NTSTATUS status,
				    uint8_t error_context_count,
				    DATA_BLOB *info,
				    const char *location)
{
	struct smbXsrv_connection *xconn = req->xconn;
	DATA_BLOB body;
	DATA_BLOB _dyn;
	uint8_t *outhdr = SMBD_SMB2_OUT_HDR_PTR(req);
	size_t unread_bytes = smbd_smb2_unread_bytes(req);

	DBG_NOTICE("idx[%d] status[%s] |%s| "
		   "at %s\n", req->current_idx, nt_errstr(status),
		   info ? " +info" : "", location);

	if (unread_bytes) {
		/* Recvfile error. Drain incoming socket. */
		size_t ret;

		errno = 0;
		ret = drain_socket(xconn->transport.sock, unread_bytes);
		if (ret != unread_bytes) {
			NTSTATUS error;

			if (errno == 0) {
				error = NT_STATUS_IO_DEVICE_ERROR;
			} else {
				error = map_nt_error_from_unix_common(errno);
			}

			DEBUG(2, ("Failed to drain %u bytes from SMB2 socket: "
				  "ret[%u] errno[%d] => %s\n",
				  (unsigned)unread_bytes,
				  (unsigned)ret, errno, nt_errstr(error)));
			return error;
		}
	}

	if (req->compound_related &&
	    NT_STATUS_EQUAL(status, NT_STATUS_CANCELLED))
	{
		/*
		 * A compound request went async but was cancelled as it was not
		 * one of the allowed async compound requests.
		 */
		status = NT_STATUS_INTERNAL_ERROR;
	}

	body.data = outhdr + SMB2_HDR_BODY;
	body.length = 8;
	SSVAL(body.data, 0, 9);
	SCVAL(body.data, 2, error_context_count);

	if (info) {
		SIVAL(body.data, 0x04, info->length);
	} else {
		/* Allocated size of req->out.vector[i].iov_base
		 * *MUST BE* OUTVEC_ALLOC_SIZE. So we have room for
		 * 1 byte without having to do an alloc.
		 */
		info = &_dyn;
		info->data = ((uint8_t *)outhdr) +
			OUTVEC_ALLOC_SIZE - 1;
		info->length = 1;
		SCVAL(info->data, 0, 0);
	}

	/*
	 * Note: Even if there is an error, continue to process the request.
	 * per MS-SMB2.
	 */

	return smbd_smb2_request_done_ex(req, status, body, info, __location__);
}

struct smbd_smb2_break_state {
	struct tevent_req *req;
	struct smbd_smb2_send_queue queue_entry;
	uint8_t nbt_hdr[NBT_HDR_SIZE];
	uint8_t hdr[SMB2_HDR_BODY];
	struct iovec vector[1+SMBD_SMB2_NUM_IOV_PER_REQ];
};

static struct tevent_req *smbd_smb2_break_send(TALLOC_CTX *mem_ctx,
					       struct tevent_context *ev,
					       struct smbXsrv_connection *xconn,
					       uint64_t session_id,
					       const uint8_t *body,
					       size_t body_len)
{
	struct tevent_req *req = NULL;
	struct smbd_smb2_break_state *state = NULL;
	NTSTATUS status;
	bool ok;

	req = tevent_req_create(mem_ctx, &state,
				struct smbd_smb2_break_state);
	if (req == NULL) {
		return NULL;
	}

	state->req = req;
	tevent_req_defer_callback(req, ev);

	SIVAL(state->hdr, 0,				SMB2_MAGIC);
	SSVAL(state->hdr, SMB2_HDR_LENGTH,		SMB2_HDR_BODY);
	SSVAL(state->hdr, SMB2_HDR_EPOCH,		0);
	SIVAL(state->hdr, SMB2_HDR_STATUS,		0);
	SSVAL(state->hdr, SMB2_HDR_OPCODE,		SMB2_OP_BREAK);
	SSVAL(state->hdr, SMB2_HDR_CREDIT,		0);
	SIVAL(state->hdr, SMB2_HDR_FLAGS,		SMB2_HDR_FLAG_REDIRECT);
	SIVAL(state->hdr, SMB2_HDR_NEXT_COMMAND,	0);
	SBVAL(state->hdr, SMB2_HDR_MESSAGE_ID,		UINT64_MAX);
	SIVAL(state->hdr, SMB2_HDR_PID,			0);
	SIVAL(state->hdr, SMB2_HDR_TID,			0);
	SBVAL(state->hdr, SMB2_HDR_SESSION_ID,		session_id);
	memset(state->hdr+SMB2_HDR_SIGNATURE, 0, 16);

	state->vector[0] = (struct iovec) {
		.iov_base = state->nbt_hdr,
		.iov_len  = sizeof(state->nbt_hdr)
	};

	state->vector[1+SMBD_SMB2_TF_IOV_OFS] = (struct iovec) {
		.iov_base = NULL,
		.iov_len  = 0
	};

	state->vector[1+SMBD_SMB2_HDR_IOV_OFS] = (struct iovec) {
		.iov_base = state->hdr,
		.iov_len  = sizeof(state->hdr)
	};

	state->vector[1+SMBD_SMB2_BODY_IOV_OFS] = (struct iovec) {
		.iov_base = discard_const_p(uint8_t, body),
		.iov_len  = body_len,
	};

	/*
	 * state->vector[1+SMBD_SMB2_DYN_IOV_OFS] is NULL by talloc_zero above
	 */

	ok = smb2_setup_nbt_length(state->vector,
				   1 + SMBD_SMB2_NUM_IOV_PER_REQ);
	if (!ok) {
		tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
		return tevent_req_post(req, ev);
	}

	/*
	 * We require TCP acks for this PDU to the client!
	 * We want 5 retransmissions and timeout when the
	 * retransmission timeout (rto) passed 6 times.
	 *
	 * required_acked_bytes gets a dummy value of
	 * UINT64_MAX, as long it's in xconn->smb2.send_queue,
	 * it'll get the real value when it's moved to
	 * xconn->ack.queue.
	 *
	 * state->queue_entry.ack.req gets completed with
	 * 1.  tevent_req_done(), when all bytes are acked.
	 * 2a. tevent_req_nterror(NT_STATUS_IO_TIMEOUT), when
	 *     the timeout expired before all bytes were acked.
	 * 2b. tevent_req_nterror(transport_error), when the
	 *     connection got a disconnect from the kernel.
	 */
	state->queue_entry.ack.timeout =
		timeval_current_ofs_usec(xconn->ack.rto_usecs * 6);
	state->queue_entry.ack.required_acked_bytes = UINT64_MAX;
	state->queue_entry.ack.req = req;
	state->queue_entry.mem_ctx = state;
	state->queue_entry.vector = state->vector;
	state->queue_entry.count = ARRAY_SIZE(state->vector);
	DLIST_ADD_END(xconn->smb2.send_queue, &state->queue_entry);
	xconn->smb2.send_queue_len++;

	status = smbd_smb2_flush_send_queue(xconn);
	if (!NT_STATUS_IS_OK(status)) {
		if (tevent_req_is_in_progress(req)) {
			/*
			 * most likely tevent_req_nterror()
			 * was already called on
			 * state->queue_entry.ack.req
			 */
			tevent_req_nterror(req, status);
		}
		return tevent_req_post(req, ev);
	}

	return req;
}

static NTSTATUS smbd_smb2_break_recv(struct tevent_req *req)
{
	return tevent_req_simple_recv_ntstatus(req);
}

struct smbXsrv_pending_break {
	struct smbXsrv_pending_break *prev, *next;
	struct smbXsrv_client *client;
	bool disable_oplock_break_retries;
	uint64_t session_id;
	uint64_t last_channel_id;
	union {
		uint8_t generic[1];
		uint8_t oplock[0x18];
		uint8_t lease[0x2c];
	} body;
	size_t body_len;
};

static void smbXsrv_pending_break_done(struct tevent_req *subreq);

static struct smbXsrv_pending_break *smbXsrv_pending_break_create(
		struct smbXsrv_client *client,
		uint64_t session_id)
{
	struct smbXsrv_pending_break *pb = NULL;

	pb = talloc_zero(client, struct smbXsrv_pending_break);
	if (pb == NULL) {
		return NULL;
	}
	pb->client = client;
	pb->session_id = session_id;
	pb->disable_oplock_break_retries = lp_smb2_disable_oplock_break_retry();

	return pb;
}

static NTSTATUS smbXsrv_pending_break_submit(struct smbXsrv_pending_break *pb);

static NTSTATUS smbXsrv_pending_break_schedule(struct smbXsrv_pending_break *pb)
{
	struct smbXsrv_client *client = pb->client;
	NTSTATUS status;

	DLIST_ADD_END(client->pending_breaks, pb);
	status = smbXsrv_client_pending_breaks_updated(client);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	status = smbXsrv_pending_break_submit(pb);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	return NT_STATUS_OK;
}

static NTSTATUS smbXsrv_pending_break_submit(struct smbXsrv_pending_break *pb)
{
	struct smbXsrv_client *client = pb->client;
	struct smbXsrv_session *session = NULL;
	struct smbXsrv_connection *xconn = NULL;
	struct smbXsrv_connection *oplock_xconn = NULL;
	struct tevent_req *subreq = NULL;
	NTSTATUS status;

	if (pb->session_id != 0) {
		status = get_valid_smbXsrv_session(client,
						   pb->session_id,
						   &session);
		if (NT_STATUS_EQUAL(status, NT_STATUS_USER_SESSION_DELETED)) {
			return NT_STATUS_ABANDONED;
		}
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}

		if (pb->last_channel_id != 0) {
			/*
			 * This is what current Windows servers
			 * do, they don't retry on all available
			 * channels. They only use the last channel.
			 *
			 * But it doesn't match the specification in
			 * [MS-SMB2] "3.3.4.6 Object Store Indicates an
			 * Oplock Break"
			 *
			 * Per default disable_oplock_break_retries is false
			 * and we behave like the specification.
			 */
			if (pb->disable_oplock_break_retries) {
				return NT_STATUS_ABANDONED;
			}
		}
	}

	for (xconn = client->connections; xconn != NULL; xconn = xconn->next) {
		if (!NT_STATUS_IS_OK(xconn->transport.status)) {
			continue;
		}

		if (xconn->channel_id == 0) {
			/*
			 * non-multichannel case
			 */
			break;
		}

		if (session != NULL) {
			struct smbXsrv_channel_global0 *c = NULL;

			/*
			 * Having a session means we're handling
			 * an oplock break and we only need to
			 * use channels available on the
			 * session.
			 */
			status = smbXsrv_session_find_channel(session, xconn, &c);
			if (!NT_STATUS_IS_OK(status)) {
				continue;
			}

			/*
			 * This is what current Windows servers
			 * do, they don't retry on all available
			 * channels. They only use the last channel.
			 *
			 * But it doesn't match the specification
			 * in [MS-SMB2] "3.3.4.6 Object Store Indicates an
			 * Oplock Break"
			 *
			 * Per default disable_oplock_break_retries is false
			 * and we behave like the specification.
			 */
			if (pb->disable_oplock_break_retries) {
				oplock_xconn = xconn;
				continue;
			}
		}

		if (xconn->channel_id > pb->last_channel_id) {
			/*
			 * multichannel case
			 */
			break;
		}
	}

	if (xconn == NULL) {
		xconn = oplock_xconn;
	}

	if (xconn == NULL) {
		/*
		 * If there's no remaining connection available
		 * tell the caller to stop...
		 */
		return NT_STATUS_ABANDONED;
	}

	pb->last_channel_id = xconn->channel_id;

	subreq = smbd_smb2_break_send(pb,
				      client->raw_ev_ctx,
				      xconn,
				      pb->session_id,
				      pb->body.generic,
				      pb->body_len);
	if (subreq == NULL) {
		return NT_STATUS_NO_MEMORY;
	}
	tevent_req_set_callback(subreq,
				smbXsrv_pending_break_done,
				pb);

	return NT_STATUS_OK;
}

static void smbXsrv_pending_break_done(struct tevent_req *subreq)
{
	struct smbXsrv_pending_break *pb =
		tevent_req_callback_data(subreq,
		struct smbXsrv_pending_break);
	struct smbXsrv_client *client = pb->client;
	NTSTATUS status;

	status = smbd_smb2_break_recv(subreq);
	TALLOC_FREE(subreq);
	if (!NT_STATUS_IS_OK(status)) {
		struct smbXsrv_connection *xconn = NULL;

		/*
		 * smbXsrv_connection_disconnect_transport()
		 * was already called, but in order to
		 * avoid stale xconn structures and in
		 * case of only one xconn a stale process
		 * we need to call smbd_server_connection_terminate()
		 *
		 * So we search if the xconn related to the
		 * last_channel_id is still there and
		 * start the cleanup, which most likely
		 * happens in an async fashion
		 */
		for (xconn = client->connections;
		     xconn != NULL;
		     xconn = xconn->next)
		{
			if (xconn->channel_id != pb->last_channel_id) {
				continue;
			}

			smbd_server_connection_terminate(xconn,
							 nt_errstr(status));
			break;
		}

		status = smbXsrv_pending_break_submit(pb);
		if (NT_STATUS_EQUAL(status, NT_STATUS_ABANDONED)) {
			/*
			 * If there's no remaining connection
			 * there's no need to send a break again.
			 */
			goto remove;
		}
		if (!NT_STATUS_IS_OK(status)) {
			smbd_server_disconnect_client(client, nt_errstr(status));
			return;
		}
		return;
	}

remove:
	DLIST_REMOVE(client->pending_breaks, pb);
	TALLOC_FREE(pb);

	status = smbXsrv_client_pending_breaks_updated(client);
	if (!NT_STATUS_IS_OK(status)) {
		smbd_server_disconnect_client(client, nt_errstr(status));
		return;
	}
}

NTSTATUS smbd_smb2_send_oplock_break(struct smbXsrv_client *client,
				     struct smbXsrv_open *op,
				     uint8_t oplock_level)
{
	struct smbXsrv_pending_break *pb = NULL;
	uint8_t *body = NULL;

	pb = smbXsrv_pending_break_create(client,
					  op->compat->vuid);
	if (pb == NULL) {
		return NT_STATUS_NO_MEMORY;
	}
	pb->body_len = sizeof(pb->body.oplock);
	body = pb->body.oplock;

	SSVAL(body, 0x00, pb->body_len);
	SCVAL(body, 0x02, oplock_level);
	SCVAL(body, 0x03, 0);		/* reserved */
	SIVAL(body, 0x04, 0);		/* reserved */
	SBVAL(body, 0x08, op->global->open_persistent_id);
	SBVAL(body, 0x10, op->global->open_volatile_id);

	return smbXsrv_pending_break_schedule(pb);
}

NTSTATUS smbd_smb2_send_lease_break(struct smbXsrv_client *client,
				    uint16_t new_epoch,
				    uint32_t lease_flags,
				    struct smb2_lease_key *lease_key,
				    uint32_t current_lease_state,
				    uint32_t new_lease_state)
{
	struct smbXsrv_pending_break *pb = NULL;
	uint8_t *body = NULL;

	pb = smbXsrv_pending_break_create(client,
					  0); /* no session_id */
	if (pb == NULL) {
		return NT_STATUS_NO_MEMORY;
	}
	pb->body_len = sizeof(pb->body.lease);
	body = pb->body.lease;

	SSVAL(body, 0x00, pb->body_len);
	SSVAL(body, 0x02, new_epoch);
	SIVAL(body, 0x04, lease_flags);
	SBVAL(body, 0x08, lease_key->data[0]);
	SBVAL(body, 0x10, lease_key->data[1]);
	SIVAL(body, 0x18, current_lease_state);
	SIVAL(body, 0x1c, new_lease_state);
	SIVAL(body, 0x20, 0);		/* BreakReason, MUST be 0 */
	SIVAL(body, 0x24, 0);		/* AccessMaskHint, MUST be 0 */
	SIVAL(body, 0x28, 0);		/* ShareMaskHint, MUST be 0 */

	return smbXsrv_pending_break_schedule(pb);
}

static bool is_smb2_recvfile_write(struct smbd_smb2_request_read_state *state)
{
	NTSTATUS status;
	uint32_t flags;
	uint64_t file_id_persistent;
	uint64_t file_id_volatile;
	struct smbXsrv_open *op = NULL;
	struct files_struct *fsp = NULL;
	const uint8_t *body = NULL;

	/*
	 * This is only called with a pktbuf
	 * of at least SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN
	 * bytes
	 */

	if (IVAL(state->pktbuf, 0) == SMB2_TF_MAGIC) {
		/* Transform header. Cannot recvfile. */
		return false;
	}
	if (IVAL(state->pktbuf, 0) != SMB2_MAGIC) {
		/* Not SMB2. Normal error path will cope. */
		return false;
	}
	if (SVAL(state->pktbuf, 4) != SMB2_HDR_BODY) {
		/* Not SMB2. Normal error path will cope. */
		return false;
	}
	if (SVAL(state->pktbuf, SMB2_HDR_OPCODE) != SMB2_OP_WRITE) {
		/* Needs to be a WRITE. */
		return false;
	}
	if (IVAL(state->pktbuf, SMB2_HDR_NEXT_COMMAND) != 0) {
		/* Chained. Cannot recvfile. */
		return false;
	}
	flags = IVAL(state->pktbuf, SMB2_HDR_FLAGS);
	if (flags & SMB2_HDR_FLAG_CHAINED) {
		/* Chained. Cannot recvfile. */
		return false;
	}
	if (flags & SMB2_HDR_FLAG_SIGNED) {
		/* Signed. Cannot recvfile. */
		return false;
	}

	body = &state->pktbuf[SMB2_HDR_BODY];

	file_id_persistent	= BVAL(body, 0x10);
	file_id_volatile	= BVAL(body, 0x18);

	status = smb2srv_open_lookup(state->req->xconn,
				     file_id_persistent,
				     file_id_volatile,
				     0, /* now */
				     &op);
	if (!NT_STATUS_IS_OK(status)) {
		return false;
	}

	fsp = op->compat;
	if (fsp == NULL) {
		return false;
	}
	if (fsp->conn == NULL) {
		return false;
	}

	if (IS_IPC(fsp->conn)) {
		return false;
	}
	if (IS_PRINT(fsp->conn)) {
		return false;
	}
	if (fsp_is_alternate_stream(fsp)) {
		return false;
	}

	DEBUG(10,("Doing recvfile write len = %u\n",
		(unsigned int)(state->pktfull - state->pktlen)));

	return true;
}

static NTSTATUS smbd_smb2_request_next_incoming(struct smbXsrv_connection *xconn)
{
	struct smbd_smb2_request_read_state *state = &xconn->smb2.request_read_state;
	struct smbd_smb2_request *req = NULL;
	size_t max_send_queue_len;
	size_t cur_send_queue_len;

	if (!NT_STATUS_IS_OK(xconn->transport.status)) {
		/*
		 * we're not supposed to do any io
		 */
		return NT_STATUS_OK;
	}

	if (state->req != NULL) {
		/*
		 * if there is already a tstream_readv_pdu
		 * pending, we are done.
		 */
		return NT_STATUS_OK;
	}

	max_send_queue_len = MAX(1, xconn->smb2.credits.max/16);
	cur_send_queue_len = xconn->smb2.send_queue_len;

	if (cur_send_queue_len > max_send_queue_len) {
		/*
		 * if we have a lot of requests to send,
		 * we wait until they are on the wire until we
		 * ask for the next request.
		 */
		return NT_STATUS_OK;
	}

	/* ask for the next request */
	req = smbd_smb2_request_allocate(xconn);
	if (req == NULL) {
		return NT_STATUS_NO_MEMORY;
	}
	*state = (struct smbd_smb2_request_read_state) {
		.req = req,
		.min_recv_size = lp_min_receive_file_size(),
		._vector = {
			[0] = (struct iovec) {
				.iov_base = (void *)state->hdr.nbt,
				.iov_len = NBT_HDR_SIZE,
			},
		},
		.vector = state->_vector,
		.count = 1,
	};

	TEVENT_FD_READABLE(xconn->transport.fde);

	return NT_STATUS_OK;
}

NTSTATUS smbd_smb2_process_negprot(struct smbXsrv_connection *xconn,
			       uint64_t expected_seq_low,
			       const uint8_t *inpdu, size_t size)
{
	struct smbd_server_connection *sconn = xconn->client->sconn;
	NTSTATUS status;
	struct smbd_smb2_request *req = NULL;

	DBG_DEBUG("packet length %zu\n", size);

	status = smbd_initialize_smb2(xconn, expected_seq_low);
	if (!NT_STATUS_IS_OK(status)) {
		smbd_server_connection_terminate(xconn, nt_errstr(status));
		return status;
	}

	/*
	 * If a new connection joins the process, when we're
	 * already in a "pending break cycle", we need to
	 * turn on the ack checker on the new connection.
	 */
	status = smbXsrv_client_pending_breaks_updated(xconn->client);
	if (!NT_STATUS_IS_OK(status)) {
		/*
		 * If there's a problem, we disconnect the whole
		 * client with all connections here!
		 *
		 * Instead of just the new connection.
		 */
		smbd_server_disconnect_client(xconn->client, nt_errstr(status));
		return status;
	}

	status = smbd_smb2_request_create(xconn, inpdu, size, &req);
	if (!NT_STATUS_IS_OK(status)) {
		smbd_server_connection_terminate(xconn, nt_errstr(status));
		return status;
	}

	status = smbd_smb2_request_validate(req);
	if (!NT_STATUS_IS_OK(status)) {
		smbd_server_connection_terminate(xconn, nt_errstr(status));
		return status;
	}

	status = smbd_smb2_request_setup_out(req);
	if (!NT_STATUS_IS_OK(status)) {
		smbd_server_connection_terminate(xconn, nt_errstr(status));
		return status;
	}

#ifdef WITH_PROFILE
	/*
	 * this was already counted at the SMB1 layer =>
	 * smbd_smb2_request_dispatch() should not count it twice.
	 */
	if (profile_p->values.request_stats.count > 0) {
		profile_p->values.request_stats.count--;
	}
#endif
	status = smbd_smb2_request_dispatch(req);
	if (!NT_STATUS_IS_OK(status)) {
		smbd_server_connection_terminate(xconn, nt_errstr(status));
		return status;
	}

	status = smbd_smb2_request_next_incoming(xconn);
	if (!NT_STATUS_IS_OK(status)) {
		smbd_server_connection_terminate(xconn, nt_errstr(status));
		return status;
	}

	sconn->num_requests++;
	return NT_STATUS_OK;
}

static int socket_error_from_errno(int ret,
				   int sys_errno,
				   bool *retry)
{
	*retry = false;

	if (ret >= 0) {
		return 0;
	}

	if (ret != -1) {
		return EIO;
	}

	if (sys_errno == 0) {
		return EIO;
	}

	if (sys_errno == EINTR) {
		*retry = true;
		return sys_errno;
	}

	if (sys_errno == EINPROGRESS) {
		*retry = true;
		return sys_errno;
	}

	if (sys_errno == EAGAIN) {
		*retry = true;
		return sys_errno;
	}

	/* ENOMEM is retryable on Solaris/illumos, and possibly other systems. */
	if (sys_errno == ENOMEM) {
		*retry = true;
		return sys_errno;
	}

#ifdef EWOULDBLOCK
#if EWOULDBLOCK != EAGAIN
	if (sys_errno == EWOULDBLOCK) {
		*retry = true;
		return sys_errno;
	}
#endif
#endif

	return sys_errno;
}

static NTSTATUS smbd_smb2_advance_send_queue(struct smbXsrv_connection *xconn,
					     struct smbd_smb2_send_queue **_e,
					     size_t n)
{
	struct smbd_smb2_send_queue *e = *_e;
	bool ok;

	xconn->ack.unacked_bytes += n;

	ok = iov_advance(&e->vector, &e->count, n);
	if (!ok) {
		return NT_STATUS_INTERNAL_ERROR;
	}

	if (e->count > 0) {
		return NT_STATUS_RETRY;
	}

	xconn->smb2.send_queue_len--;
	DLIST_REMOVE(xconn->smb2.send_queue, e);

	if (e->ack.req == NULL) {
		*_e = NULL;
		talloc_free(e->mem_ctx);
		return NT_STATUS_OK;
	}

	e->ack.required_acked_bytes = xconn->ack.unacked_bytes;
	DLIST_ADD_END(xconn->ack.queue, e);

	return NT_STATUS_OK;
}

static NTSTATUS smbd_smb2_flush_with_sendmsg(struct smbXsrv_connection *xconn)
{
	int ret;
	int err;
	bool retry;
	NTSTATUS status;

	if (xconn->smb2.send_queue == NULL) {
		TEVENT_FD_NOT_WRITEABLE(xconn->transport.fde);
		return NT_STATUS_OK;
	}

	while (xconn->smb2.send_queue != NULL) {
		struct smbd_smb2_send_queue *e = xconn->smb2.send_queue;
		unsigned sendmsg_flags = 0;

		if (!NT_STATUS_IS_OK(xconn->transport.status)) {
			/*
			 * we're not supposed to do any io
			 * just flush all pending stuff.
			 */
			xconn->smb2.send_queue_len--;
			DLIST_REMOVE(xconn->smb2.send_queue, e);

			talloc_free(e->mem_ctx);
			continue;
		}

		if (e->sendfile_header != NULL) {
			size_t size = 0;
			size_t i = 0;
			uint8_t *buf;

			status = NT_STATUS_INTERNAL_ERROR;

			for (i=0; i < e->count; i++) {
				size += e->vector[i].iov_len;
			}

			if (size <= e->sendfile_header->length) {
				buf = e->sendfile_header->data;
			} else {
				buf = talloc_array(e->mem_ctx, uint8_t, size);
				if (buf == NULL) {
					return NT_STATUS_NO_MEMORY;
				}
			}

			size = 0;
			for (i=0; i < e->count; i++) {
				memcpy(buf+size,
				       e->vector[i].iov_base,
				       e->vector[i].iov_len);
				size += e->vector[i].iov_len;
			}

			e->sendfile_header->data = buf;
			e->sendfile_header->length = size;
			e->sendfile_status = &status;
			e->count = 0;

			xconn->smb2.send_queue_len--;
			DLIST_REMOVE(xconn->smb2.send_queue, e);

			size += e->sendfile_body_size;

			/*
			 * This triggers the sendfile path via
			 * the destructor.
			 */
			talloc_free(e->mem_ctx);

			if (!NT_STATUS_IS_OK(status)) {
				smbXsrv_connection_disconnect_transport(xconn,
									status);
				return status;
			}
			xconn->ack.unacked_bytes += size;
			continue;
		}

		e->msg = (struct msghdr) {
			.msg_iov = e->vector,
			.msg_iovlen = e->count,
		};

#ifdef MSG_NOSIGNAL
		sendmsg_flags |= MSG_NOSIGNAL;
#endif
#ifdef MSG_DONTWAIT
		sendmsg_flags |= MSG_DONTWAIT;
#endif

		ret = sendmsg(xconn->transport.sock, &e->msg, sendmsg_flags);
		if (ret == 0) {
			/* propagate end of file */
			return NT_STATUS_INTERNAL_ERROR;
		}
		err = socket_error_from_errno(ret, errno, &retry);
		if (retry) {
			/* retry later */
			TEVENT_FD_WRITEABLE(xconn->transport.fde);
			return NT_STATUS_OK;
		}
		if (err != 0) {
			status = map_nt_error_from_unix_common(err);
			smbXsrv_connection_disconnect_transport(xconn,
								status);
			return status;
		}

		status = smbd_smb2_advance_send_queue(xconn, &e, ret);
		if (NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
			/* retry later */
			TEVENT_FD_WRITEABLE(xconn->transport.fde);
			return NT_STATUS_OK;
		}
		if (!NT_STATUS_IS_OK(status)) {
			smbXsrv_connection_disconnect_transport(xconn,
								status);
			return status;
		}
	}

	return NT_STATUS_MORE_PROCESSING_REQUIRED;
}

static NTSTATUS smbd_smb2_flush_send_queue(struct smbXsrv_connection *xconn)
{
	NTSTATUS status;

	status = smbd_smb2_flush_with_sendmsg(xconn);
	if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
		return status;
	}

	/*
	 * Restart reads if we were blocked on
	 * draining the send queue.
	 */

	status = smbd_smb2_request_next_incoming(xconn);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	return NT_STATUS_OK;
}

static NTSTATUS smbd_smb2_advance_incoming(struct smbXsrv_connection *xconn, size_t n)
{
	struct smbd_server_connection *sconn = xconn->client->sconn;
	struct smbd_smb2_request_read_state *state = &xconn->smb2.request_read_state;
	struct smbd_smb2_request *req = NULL;
	size_t min_recvfile_size = UINT32_MAX;
	NTSTATUS status;
	NTTIME now;
	bool ok;

	ok = iov_advance(&state->vector, &state->count, n);
	if (!ok) {
		return NT_STATUS_INTERNAL_ERROR;
	}

	if (state->count > 0) {
		return NT_STATUS_PENDING;
	}

	if (state->pktlen > 0) {
		if (!state->doing_receivefile) {
			/*
			 * we have all the data.
			 */
			goto got_full;
		}

		if (!is_smb2_recvfile_write(state)) {
			size_t ofs = state->pktlen;

			/*
			 * Not a possible receivefile write.
			 * Read the rest of the data.
			 */
			state->doing_receivefile = false;

			state->pktbuf = talloc_realloc(state->req,
						       state->pktbuf,
						       uint8_t,
						       state->pktfull);
			if (state->pktbuf == NULL) {
				return NT_STATUS_NO_MEMORY;
			}

			state->_vector[0]  = (struct iovec) {
				.iov_base = (void *)(state->pktbuf + ofs),
				.iov_len = (state->pktfull - ofs),
			};
			state->vector = state->_vector;
			state->count = 1;

			state->pktlen = state->pktfull;
			return NT_STATUS_RETRY;
		}

		/*
		 * This is a receivefile write so we've
		 * done a short read.
		 */
		goto got_full;
	}

	/*
	 * Now we analyze the NBT header
	 */
	if (state->hdr.nbt[0] != 0x00) {
		state->min_recv_size = 0;
	}
	state->pktfull = smb2_len(state->hdr.nbt);
	if (state->pktfull == 0) {
		goto got_full;
	}

	if (state->min_recv_size != 0) {
		min_recvfile_size = SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN;
		min_recvfile_size += state->min_recv_size;
	}

	if (state->pktfull > min_recvfile_size) {
		/*
		 * Might be a receivefile write. Read the SMB2 HEADER +
		 * SMB2_WRITE header first. Set 'doing_receivefile'
		 * as we're *attempting* receivefile write. If this
		 * turns out not to be a SMB2_WRITE request or otherwise
		 * not suitable then we'll just read the rest of the data
		 * the next time this function is called.
		 */
		state->pktlen = SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN;
		state->doing_receivefile = true;
	} else {
		state->pktlen = state->pktfull;
	}

	state->pktbuf = talloc_array(state->req, uint8_t, state->pktlen);
	if (state->pktbuf == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	state->_vector[0] = (struct iovec) {
		.iov_base = (void *)state->pktbuf,
		.iov_len = state->pktlen,
	};
	state->vector = state->_vector;
	state->count = 1;

	return NT_STATUS_RETRY;

got_full:

	if (state->hdr.nbt[0] != 0x00) {
		DEBUG(1,("ignore NBT[0x%02X] msg\n",
			 state->hdr.nbt[0]));

		req = state->req;
		*state = (struct smbd_smb2_request_read_state) {
			.req = req,
			.min_recv_size = lp_min_receive_file_size(),
			._vector = {
				[0] = (struct iovec) {
					.iov_base = (void *)state->hdr.nbt,
					.iov_len = NBT_HDR_SIZE,
				},
			},
			.vector = state->_vector,
			.count = 1,
		};
		return NT_STATUS_RETRY;
	}

	req = state->req;

	req->request_time = timeval_current();
	now = timeval_to_nttime(&req->request_time);

	status = smbd_smb2_inbuf_parse_compound(xconn,
						now,
						state->pktbuf,
						state->pktlen,
						req,
						&req->in.vector,
						&req->in.vector_count);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	if (state->doing_receivefile) {
		req->smb1req = talloc_zero(req, struct smb_request);
		if (req->smb1req == NULL) {
			return NT_STATUS_NO_MEMORY;
		}
		req->smb1req->unread_bytes = state->pktfull - state->pktlen;
	}

	*state = (struct smbd_smb2_request_read_state) {
		.req = NULL,
	};

	req->current_idx = 1;

	DEBUG(10,("smbd_smb2_request idx[%d] of %d vectors\n",
		 req->current_idx, req->in.vector_count));

	status = smbd_smb2_request_validate(req);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	status = smbd_smb2_request_setup_out(req);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	status = smbd_smb2_request_dispatch(req);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	sconn->num_requests++;

	/* The timeout_processing function isn't run nearly
	   often enough to implement 'max log size' without
	   overrunning the size of the file by many megabytes.
	   This is especially true if we are running at debug
	   level 10.  Checking every 50 SMB2s is a nice
	   tradeoff of performance vs log file size overrun. */

	if ((sconn->num_requests % 50) == 0 &&
	    need_to_check_log_size()) {
		change_to_root_user();
		check_log_size();
	}

	status = smbd_smb2_request_next_incoming(xconn);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	return NT_STATUS_OK;
}

static NTSTATUS smbd_smb2_io_handler(struct smbXsrv_connection *xconn,
				     uint16_t fde_flags)
{
	struct smbd_smb2_request_read_state *state = &xconn->smb2.request_read_state;
	unsigned recvmsg_flags = 0;
	int ret;
	int err;
	bool retry;
	NTSTATUS status;

	if (!NT_STATUS_IS_OK(xconn->transport.status)) {
		/*
		 * we're not supposed to do any io
		 */
		TEVENT_FD_NOT_READABLE(xconn->transport.fde);
		TEVENT_FD_NOT_WRITEABLE(xconn->transport.fde);
		TEVENT_FD_NOT_WANTERROR(xconn->transport.fde);
		return NT_STATUS_OK;
	}

	if (fde_flags & TEVENT_FD_ERROR) {
		ret = samba_socket_poll_or_sock_error(xconn->transport.sock);
		if (ret == -1) {
			err = errno;
			status = map_nt_error_from_unix_common(err);
			smbXsrv_connection_disconnect_transport(xconn,
								status);
			return status;
		}
		/* This should not happen */
		status = NT_STATUS_REMOTE_DISCONNECT;
		smbXsrv_connection_disconnect_transport(xconn,
							status);
		return status;
	}

	if (fde_flags & TEVENT_FD_WRITE) {
		status = smbd_smb2_flush_send_queue(xconn);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}
	}

	if (!(fde_flags & TEVENT_FD_READ)) {
		return NT_STATUS_OK;
	}

	if (state->req == NULL) {
		TEVENT_FD_NOT_READABLE(xconn->transport.fde);
		return NT_STATUS_OK;
	}

again:

	state->msg = (struct msghdr) {
		.msg_iov = state->vector,
		.msg_iovlen = state->count,
	};

#ifdef MSG_NOSIGNAL
	recvmsg_flags |= MSG_NOSIGNAL;
#endif
#ifdef MSG_DONTWAIT
	recvmsg_flags |= MSG_DONTWAIT;
#endif

	ret = recvmsg(xconn->transport.sock, &state->msg, recvmsg_flags);
	if (ret == 0) {
		/* propagate end of file */
		status = NT_STATUS_END_OF_FILE;
		smbXsrv_connection_disconnect_transport(xconn,
							status);
		return status;
	}
	err = socket_error_from_errno(ret, errno, &retry);
	if (retry) {
		/* retry later */
		TEVENT_FD_READABLE(xconn->transport.fde);
		return NT_STATUS_OK;
	}
	if (err != 0) {
		status = map_nt_error_from_unix_common(err);
		smbXsrv_connection_disconnect_transport(xconn,
							status);
		return status;
	}

	status = smbd_smb2_advance_incoming(xconn, ret);
	if (NT_STATUS_EQUAL(status, NT_STATUS_PENDING)) {
		/* we have more to read */
		TEVENT_FD_READABLE(xconn->transport.fde);
		return NT_STATUS_OK;
	}
	if (NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
		/*
		 * smbd_smb2_advance_incoming setup a new vector
		 * that we should try to read immediately.
		 */
		goto again;
	}
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	return NT_STATUS_OK;
}

static void smbd_smb2_connection_handler(struct tevent_context *ev,
					 struct tevent_fd *fde,
					 uint16_t flags,
					 void *private_data)
{
	struct smbXsrv_connection *xconn =
		talloc_get_type_abort(private_data,
		struct smbXsrv_connection);
	NTSTATUS status;

	status = smbd_smb2_io_handler(xconn, flags);
	if (!NT_STATUS_IS_OK(status)) {
		smbd_server_connection_terminate(xconn, nt_errstr(status));
		return;
	}
}
