/*
   Unix SMB/CIFS implementation.
   Core SMB2 server

   Copyright (C) Stefan Metzmacher 2009

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

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

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

#include "includes.h"
#include "smbd/smbd.h"
#include "smbd/globals.h"
#include "../libcli/smb/smb_common.h"
#include "../libcli/smb/smb2_negotiate_context.h"
#include "../lib/tsocket/tsocket.h"
#include "../librpc/ndr/libndr.h"
#include "../libcli/smb/smb_signing.h"
#include "auth.h"
#include "auth/gensec/gensec.h"
#include "lib/util/string_wrappers.h"

#undef DBGC_CLASS
#define DBGC_CLASS DBGC_SMB2

extern fstring remote_proto;

/*
 * this is the entry point if SMB2 is selected via
 * the SMB negprot and the given dialect.
 */
static NTSTATUS reply_smb20xx(struct smb_request *req, uint16_t dialect)
{
	uint8_t *smb2_inpdu;
	uint8_t *smb2_hdr;
	uint8_t *smb2_body;
	uint8_t *smb2_dyn;
	size_t len = SMB2_HDR_BODY + 0x24 + 2;

	smb2_inpdu = talloc_zero_array(talloc_tos(), uint8_t, len);
	if (smb2_inpdu == NULL) {
		DEBUG(0, ("Could not push spnego blob\n"));
		reply_nterror(req, NT_STATUS_NO_MEMORY);
		return NT_STATUS_NO_MEMORY;
	}
	smb2_hdr = smb2_inpdu;
	smb2_body = smb2_hdr + SMB2_HDR_BODY;
	smb2_dyn = smb2_body + 0x24;

	SIVAL(smb2_hdr, SMB2_HDR_PROTOCOL_ID,	SMB2_MAGIC);
	SIVAL(smb2_hdr, SMB2_HDR_LENGTH,	SMB2_HDR_BODY);

	SSVAL(smb2_body, 0x00, 0x0024);	/* struct size */
	SSVAL(smb2_body, 0x02, 0x0001);	/* dialect count */

	SSVAL(smb2_dyn,  0x00, dialect);

	req->outbuf = NULL;

	return smbd_smb2_process_negprot(req->xconn, 0, smb2_inpdu, len);
}

/*
 * this is the entry point if SMB2 is selected via
 * the SMB negprot and the "SMB 2.002" dialect.
 */
NTSTATUS reply_smb2002(struct smb_request *req, uint16_t choice)
{
	return reply_smb20xx(req, SMB2_DIALECT_REVISION_202);
}

/*
 * this is the entry point if SMB2 is selected via
 * the SMB negprot and the "SMB 2.???" dialect.
 */
NTSTATUS reply_smb20ff(struct smb_request *req, uint16_t choice)
{
	struct smbXsrv_connection *xconn = req->xconn;
	xconn->smb2.allow_2ff = true;
	return reply_smb20xx(req, SMB2_DIALECT_REVISION_2FF);
}

enum protocol_types smbd_smb2_protocol_dialect_match(const uint8_t *indyn,
				const int dialect_count,
				uint16_t *dialect)
{
	struct {
		enum protocol_types proto;
		uint16_t dialect;
	} pd[] = {
		{ PROTOCOL_SMB3_11, SMB3_DIALECT_REVISION_311 },
		{ PROTOCOL_SMB3_02, SMB3_DIALECT_REVISION_302 },
		{ PROTOCOL_SMB3_00, SMB3_DIALECT_REVISION_300 },
		{ PROTOCOL_SMB2_10, SMB2_DIALECT_REVISION_210 },
		{ PROTOCOL_SMB2_02, SMB2_DIALECT_REVISION_202 },
	};
	size_t i;

	for (i = 0; i < ARRAY_SIZE(pd); i ++) {
		int c = 0;

		if (lp_server_max_protocol() < pd[i].proto) {
			continue;
		}
		if (lp_server_min_protocol() > pd[i].proto) {
			continue;
		}

		for (c = 0; c < dialect_count; c++) {
			*dialect = SVAL(indyn, c*2);
			if (*dialect == pd[i].dialect) {
				return pd[i].proto;
			}
		}
	}

	return PROTOCOL_NONE;
}

struct smbd_smb2_request_process_negprot_state {
	struct smbd_smb2_request *req;
	DATA_BLOB outbody;
	DATA_BLOB outdyn;
};

static void smbd_smb2_request_process_negprot_mc_done(struct tevent_req *subreq);

NTSTATUS smbd_smb2_request_process_negprot(struct smbd_smb2_request *req)
{
	struct smbd_smb2_request_process_negprot_state *state = NULL;
	struct smbXsrv_connection *xconn = req->xconn;
	struct tevent_req *subreq = NULL;
	NTSTATUS status;
	const uint8_t *inbody;
	const uint8_t *indyn = NULL;
	DATA_BLOB outbody;
	DATA_BLOB outdyn;
	DATA_BLOB negprot_spnego_blob;
	uint16_t security_offset;
	DATA_BLOB security_buffer;
	size_t expected_dyn_size = 0;
	size_t c;
	uint16_t security_mode;
	uint16_t dialect_count;
	uint16_t in_security_mode;
	uint32_t in_capabilities;
	DATA_BLOB in_guid_blob;
	struct GUID in_guid;
	struct smb2_negotiate_contexts in_c = { .num_contexts = 0, };
	struct smb2_negotiate_context *in_preauth = NULL;
	struct smb2_negotiate_context *in_cipher = NULL;
	struct smb2_negotiate_context *in_sign_algo = NULL;
	struct smb2_negotiate_contexts out_c = { .num_contexts = 0, };
	struct smb2_negotiate_context *in_posix = NULL;
	const struct smb311_capabilities default_smb3_capabilities =
		smb311_capabilities_parse("server",
			lp_server_smb3_signing_algorithms(),
			lp_server_smb3_encryption_algorithms());
	DATA_BLOB out_negotiate_context_blob = data_blob_null;
	uint32_t out_negotiate_context_offset = 0;
	uint16_t out_negotiate_context_count = 0;
	uint16_t dialect = 0;
	uint32_t capabilities;
	DATA_BLOB out_guid_blob;
	struct GUID out_guid;
	enum protocol_types protocol = PROTOCOL_NONE;
	uint32_t max_limit;
	uint32_t max_trans = lp_smb2_max_trans();
	uint32_t max_read = lp_smb2_max_read();
	uint32_t max_write = lp_smb2_max_write();
	NTTIME now = timeval_to_nttime(&req->request_time);
	bool ok;

	status = smbd_smb2_request_verify_sizes(req, 0x24);
	if (!NT_STATUS_IS_OK(status)) {
		return smbd_smb2_request_error(req, status);
	}
	inbody = SMBD_SMB2_IN_BODY_PTR(req);

	dialect_count = SVAL(inbody, 0x02);

	in_security_mode = SVAL(inbody, 0x04);
	in_capabilities = IVAL(inbody, 0x08);
	in_guid_blob = data_blob_const(inbody + 0x0C, 16);

	if (dialect_count == 0) {
		return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
	}

	status = GUID_from_ndr_blob(&in_guid_blob, &in_guid);
	if (!NT_STATUS_IS_OK(status)) {
		return smbd_smb2_request_error(req, status);
	}

	expected_dyn_size = dialect_count * 2;
	if (SMBD_SMB2_IN_DYN_LEN(req) < expected_dyn_size) {
		return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
	}
	indyn = SMBD_SMB2_IN_DYN_PTR(req);

	protocol = smbd_smb2_protocol_dialect_match(indyn,
					dialect_count,
					&dialect);

	for (c=0; protocol == PROTOCOL_NONE && c < dialect_count; c++) {
		if (lp_server_max_protocol() < PROTOCOL_SMB2_10) {
			break;
		}

		dialect = SVAL(indyn, c*2);
		if (dialect == SMB2_DIALECT_REVISION_2FF) {
			if (xconn->smb2.allow_2ff) {
				xconn->smb2.allow_2ff = false;
				protocol = PROTOCOL_SMB2_10;
				break;
			}
		}
	}

	if (protocol == PROTOCOL_NONE) {
		return smbd_smb2_request_error(req, NT_STATUS_NOT_SUPPORTED);
	}

	if (protocol >= PROTOCOL_SMB3_11) {
		uint32_t in_negotiate_context_offset = 0;
		uint16_t in_negotiate_context_count = 0;
		DATA_BLOB in_negotiate_context_blob = data_blob_null;
		size_t ofs;

		in_negotiate_context_offset = IVAL(inbody, 0x1C);
		in_negotiate_context_count = SVAL(inbody, 0x20);

		ofs = SMB2_HDR_BODY;
		ofs += SMBD_SMB2_IN_BODY_LEN(req);
		ofs += expected_dyn_size;
		if ((ofs % 8) != 0) {
			ofs += 8 - (ofs % 8);
		}

		if (in_negotiate_context_offset != ofs) {
			return smbd_smb2_request_error(req,
					NT_STATUS_INVALID_PARAMETER);
		}

		ofs -= SMB2_HDR_BODY;
		ofs -= SMBD_SMB2_IN_BODY_LEN(req);

		if (SMBD_SMB2_IN_DYN_LEN(req) < ofs) {
			return smbd_smb2_request_error(req,
					NT_STATUS_INVALID_PARAMETER);
		}

		in_negotiate_context_blob = data_blob_const(indyn,
						SMBD_SMB2_IN_DYN_LEN(req));

		in_negotiate_context_blob.data += ofs;
		in_negotiate_context_blob.length -= ofs;

		status = smb2_negotiate_context_parse(req,
						      in_negotiate_context_blob,
						      in_negotiate_context_count,
						      &in_c);
		if (!NT_STATUS_IS_OK(status)) {
			return smbd_smb2_request_error(req, status);
		}

		if (lp_smb2_unix_extensions()) {
			in_posix = smb2_negotiate_context_find(&in_c,
					SMB2_POSIX_EXTENSIONS_AVAILABLE);

			if (in_posix != NULL) {
				const uint8_t *inbuf = in_posix->data.data;
				size_t inbuflen = in_posix->data.length;
				bool posix_found = false;
				/*
				 * For now the server only supports one variant.
				 * Check it's the right one.
				 */
				if ((inbuflen % 16) != 0) {
					return smbd_smb2_request_error(req,
						NT_STATUS_INVALID_PARAMETER);
				}
				SMB_ASSERT(strlen(SMB2_CREATE_TAG_POSIX) == 16);
				for (ofs=0; ofs<inbuflen; ofs+=16) {
					if (memcmp(inbuf+ofs,
							SMB2_CREATE_TAG_POSIX,
							16) == 0) {
						posix_found = true;
						break;
					}
				}
				if (posix_found) {
					DBG_DEBUG("Client requested SMB2 unix "
						"extensions\n");
				} else {
					DBG_DEBUG("Client requested unknown "
						"SMB2 unix extensions:\n");
					dump_data(10, inbuf, inbuflen);
					in_posix = NULL;
				}
			}
		}
	}

	if ((dialect != SMB2_DIALECT_REVISION_2FF) &&
	    (protocol >= PROTOCOL_SMB2_10) &&
	    !GUID_all_zero(&in_guid))
	{
		ok = remote_arch_cache_update(&in_guid);
		if (!ok) {
			return smbd_smb2_request_error(
				req, NT_STATUS_UNSUCCESSFUL);
		}
	}

	switch (get_remote_arch()) {
	case RA_VISTA:
	case RA_SAMBA:
	case RA_CIFSFS:
	case RA_OSX:
		break;
	default:
		set_remote_arch(RA_VISTA);
		break;
	}

	fstr_sprintf(remote_proto, "SMB%X_%02X",
		     (dialect >> 8) & 0xFF, dialect & 0xFF);

	reload_services(req->sconn, conn_snum_used, true);
	DEBUG(3,("Selected protocol %s\n", remote_proto));

	in_preauth = smb2_negotiate_context_find(&in_c,
					SMB2_PREAUTH_INTEGRITY_CAPABILITIES);
	if (protocol >= PROTOCOL_SMB3_11 && in_preauth == NULL) {
		return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
	}
	in_cipher = smb2_negotiate_context_find(&in_c,
					SMB2_ENCRYPTION_CAPABILITIES);
	in_sign_algo = smb2_negotiate_context_find(&in_c,
					SMB2_SIGNING_CAPABILITIES);

	/* negprot_spnego() returns a the server guid in the first 16 bytes */
	negprot_spnego_blob = negprot_spnego(req, xconn);
	if (negprot_spnego_blob.data == NULL) {
		return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
	}

	if (negprot_spnego_blob.length < 16) {
		return smbd_smb2_request_error(req, NT_STATUS_INTERNAL_ERROR);
	}

	security_mode = SMB2_NEGOTIATE_SIGNING_ENABLED;
	/*
	 * We use xconn->smb2.signing_mandatory set up via
	 * srv_init_signing() -> smb2_srv_init_signing().
	 * This calls lpcfg_server_signing_allowed() to get the correct
	 * defaults, e.g. signing_required for an ad_dc.
	 */
	if (xconn->smb2.signing_mandatory) {
		security_mode |= SMB2_NEGOTIATE_SIGNING_REQUIRED;
	}

	capabilities = 0;
	if (lp_host_msdfs()) {
		capabilities |= SMB2_CAP_DFS;
	}

	if (protocol >= PROTOCOL_SMB2_10 &&
	    lp_smb2_leases() &&
	    lp_oplocks(GLOBAL_SECTION_SNUM) &&
	    !lp_kernel_oplocks(GLOBAL_SECTION_SNUM))
	{
		capabilities |= SMB2_CAP_LEASING;
	}

	if ((protocol >= PROTOCOL_SMB3_00) &&
	    (lp_server_smb_encrypt(-1) != SMB_ENCRYPTION_OFF) &&
	    (in_capabilities & SMB2_CAP_ENCRYPTION)) {
		capabilities |= SMB2_CAP_ENCRYPTION;
	}

	/*
	 * 0x10000 (65536) is the maximum allowed message size
	 * for SMB 2.0
	 */
	max_limit = 0x10000;

	if (protocol >= PROTOCOL_SMB2_10) {
		int p = 0;

		if (tsocket_address_is_inet(req->sconn->local_address, "ip")) {
			p = tsocket_address_inet_port(req->sconn->local_address);
		}

		/* largeMTU is not supported over NBT (tcp port 139) */
		if (p != NBT_SMB_PORT) {
			capabilities |= SMB2_CAP_LARGE_MTU;
			xconn->smb2.credits.multicredit = true;

			/*
			 * We allow up to almost 16MB.
			 *
			 * The maximum PDU size is 0xFFFFFF (16776960)
			 * and we need some space for the header.
			 */
			max_limit = 0xFFFF00;
		}
	}

	/*
	 * the defaults are 8MB, but we'll limit this to max_limit based on
	 * the dialect (64kb for SMB 2.0, 8MB for SMB >= 2.1 with LargeMTU)
	 *
	 * user configured values exceeding the limits will be overwritten,
	 * only smaller values will be accepted
	 */

	max_trans = MIN(max_limit, lp_smb2_max_trans());
	max_read = MIN(max_limit, lp_smb2_max_read());
	max_write = MIN(max_limit, lp_smb2_max_write());

	if (in_preauth != NULL) {
		size_t needed = 4;
		uint16_t hash_count;
		uint16_t salt_length;
		uint16_t selected_preauth = 0;
		const uint8_t *p;
		uint8_t buf[38];
		size_t i;

		if (in_preauth->data.length < needed) {
			return smbd_smb2_request_error(req,
					NT_STATUS_INVALID_PARAMETER);
		}

		hash_count = SVAL(in_preauth->data.data, 0);
		salt_length = SVAL(in_preauth->data.data, 2);

		if (hash_count == 0) {
			return smbd_smb2_request_error(req,
					NT_STATUS_INVALID_PARAMETER);
		}

		p = in_preauth->data.data + needed;
		needed += hash_count * 2;
		needed += salt_length;

		if (in_preauth->data.length < needed) {
			return smbd_smb2_request_error(req,
					NT_STATUS_INVALID_PARAMETER);
		}

		for (i=0; i < hash_count; i++) {
			uint16_t v;

			v = SVAL(p, 0);
			p += 2;

			if (v == SMB2_PREAUTH_INTEGRITY_SHA512) {
				selected_preauth = v;
				break;
			}
		}

		if (selected_preauth == 0) {
			return smbd_smb2_request_error(req,
				NT_STATUS_SMB_NO_PREAUTH_INTEGRITY_HASH_OVERLAP);
		}

		SSVAL(buf, 0,  1); /* HashAlgorithmCount */
		SSVAL(buf, 2, 32); /* SaltLength */
		SSVAL(buf, 4, selected_preauth);
		generate_random_buffer(buf + 6, 32);

		status = smb2_negotiate_context_add(
			req,
			&out_c,
			SMB2_PREAUTH_INTEGRITY_CAPABILITIES,
			buf,
			sizeof(buf));
		if (!NT_STATUS_IS_OK(status)) {
			return smbd_smb2_request_error(req, status);
		}

		req->preauth = &req->xconn->smb2.preauth;
	}

	if (protocol >= PROTOCOL_SMB3_00) {
		xconn->smb2.server.sign_algo = SMB2_SIGNING_AES128_CMAC;
	} else {
		xconn->smb2.server.sign_algo = SMB2_SIGNING_HMAC_SHA256;
	}

	if ((capabilities & SMB2_CAP_ENCRYPTION) && (in_cipher != NULL)) {
		const struct smb3_encryption_capabilities *srv_ciphers =
			&default_smb3_capabilities.encryption;
		uint16_t srv_preferred_idx = UINT16_MAX;
		size_t needed = 2;
		uint16_t cipher_count;
		const uint8_t *p;
		uint8_t buf[4];
		size_t i;

		capabilities &= ~SMB2_CAP_ENCRYPTION;

		if (in_cipher->data.length < needed) {
			return smbd_smb2_request_error(req,
					NT_STATUS_INVALID_PARAMETER);
		}

		cipher_count = SVAL(in_cipher->data.data, 0);
		if (cipher_count == 0) {
			return smbd_smb2_request_error(req,
					NT_STATUS_INVALID_PARAMETER);
		}

		p = in_cipher->data.data + needed;
		needed += cipher_count * 2;

		if (in_cipher->data.length < needed) {
			return smbd_smb2_request_error(req,
					NT_STATUS_INVALID_PARAMETER);
		}

		for (i=0; i < cipher_count; i++) {
			uint16_t si;
			uint16_t v;

			v = SVAL(p, 0);
			p += 2;

			for (si = 0; si < srv_ciphers->num_algos; si++) {
				if (srv_ciphers->algos[si] != v) {
					continue;
				}

				/*
				 * The server ciphers are listed
				 * with the lowest idx being preferred.
				 */
				if (si < srv_preferred_idx) {
					srv_preferred_idx = si;
				}
				break;
			}
		}

		if (srv_preferred_idx != UINT16_MAX) {
			xconn->smb2.server.cipher =
				srv_ciphers->algos[srv_preferred_idx];
		}

		SSVAL(buf, 0, 1); /* ChiperCount */
		SSVAL(buf, 2, xconn->smb2.server.cipher);

		status = smb2_negotiate_context_add(
			req,
			&out_c,
			SMB2_ENCRYPTION_CAPABILITIES,
			buf,
			sizeof(buf));
		if (!NT_STATUS_IS_OK(status)) {
			return smbd_smb2_request_error(req, status);
		}
	}

	if (capabilities & SMB2_CAP_ENCRYPTION) {
		xconn->smb2.server.cipher = SMB2_ENCRYPTION_AES128_CCM;
	}

	if (in_sign_algo != NULL) {
		const struct smb3_signing_capabilities *srv_sign_algos =
			&default_smb3_capabilities.signing;
		uint16_t srv_preferred_idx = UINT16_MAX;
		size_t needed = 2;
		uint16_t sign_algo_count;
		const uint8_t *p;
		size_t i;

		if (in_sign_algo->data.length < needed) {
			return smbd_smb2_request_error(req,
					NT_STATUS_INVALID_PARAMETER);
		}

		sign_algo_count = SVAL(in_sign_algo->data.data, 0);
		if (sign_algo_count == 0) {
			return smbd_smb2_request_error(req,
					NT_STATUS_INVALID_PARAMETER);
		}

		p = in_sign_algo->data.data + needed;
		needed += sign_algo_count * 2;

		if (in_sign_algo->data.length < needed) {
			return smbd_smb2_request_error(req,
					NT_STATUS_INVALID_PARAMETER);
		}

		for (i=0; i < sign_algo_count; i++) {
			uint16_t si;
			uint16_t v;

			v = SVAL(p, 0);
			p += 2;

			for (si = 0; si < srv_sign_algos->num_algos; si++) {
				if (srv_sign_algos->algos[si] != v) {
					continue;
				}

				/*
				 * The server sign_algos are listed
				 * with the lowest idx being preferred.
				 */
				if (si < srv_preferred_idx) {
					srv_preferred_idx = si;
				}
				break;
			}
		}

		/*
		 * If we found a match announce it
		 * otherwise we'll keep the default
		 * of SMB2_SIGNING_AES128_CMAC
		 */
		if (srv_preferred_idx != UINT16_MAX) {
			uint8_t buf[4];

			xconn->smb2.server.sign_algo =
				srv_sign_algos->algos[srv_preferred_idx];

			SSVAL(buf, 0, 1); /* SigningAlgorithmCount */
			SSVAL(buf, 2, xconn->smb2.server.sign_algo);

			status = smb2_negotiate_context_add(
				req,
				&out_c,
				SMB2_SIGNING_CAPABILITIES,
				buf,
				sizeof(buf));
			if (!NT_STATUS_IS_OK(status)) {
				return smbd_smb2_request_error(req, status);
			}
		}
	}

	status = smb311_capabilities_check(&default_smb3_capabilities,
					   "smb2srv_negprot",
					   DBGLVL_NOTICE,
					   NT_STATUS_INVALID_PARAMETER,
					   "server",
					   protocol,
					   xconn->smb2.server.sign_algo,
					   xconn->smb2.server.cipher);
	if (!NT_STATUS_IS_OK(status)) {
		return smbd_smb2_request_error(req, status);
	}

	if (protocol >= PROTOCOL_SMB3_00 &&
	    xconn->client->server_multi_channel_enabled)
	{
		if (in_capabilities & SMB2_CAP_MULTI_CHANNEL) {
			capabilities |= SMB2_CAP_MULTI_CHANNEL;
		}
	}

	security_offset = SMB2_HDR_BODY + 0x40;

#if 1
	/* Try SPNEGO auth... */
	security_buffer = data_blob_const(negprot_spnego_blob.data + 16,
					  negprot_spnego_blob.length - 16);
#else
	/* for now we want raw NTLMSSP */
	security_buffer = data_blob_const(NULL, 0);
#endif

	if (in_posix != NULL) {
		/* Client correctly negotiated SMB2 unix extensions. */
		const uint8_t *buf = (const uint8_t *)SMB2_CREATE_TAG_POSIX;
		status = smb2_negotiate_context_add(
				req,
				&out_c,
				SMB2_POSIX_EXTENSIONS_AVAILABLE,
				buf,
				16);
		if (!NT_STATUS_IS_OK(status)) {
			return smbd_smb2_request_error(req, status);
		}
		xconn->smb2.server.posix_extensions_negotiated = true;
	}

	if (out_c.num_contexts != 0) {
		status = smb2_negotiate_context_push(req,
						&out_negotiate_context_blob,
						out_c);
		if (!NT_STATUS_IS_OK(status)) {
			return smbd_smb2_request_error(req, status);
		}
	}

	if (out_negotiate_context_blob.length != 0) {
		static const uint8_t zeros[8];
		size_t pad = 0;
		size_t ofs;

		outdyn = data_blob_dup_talloc(req, security_buffer);
		if (outdyn.length != security_buffer.length) {
			return smbd_smb2_request_error(req,
						NT_STATUS_NO_MEMORY);
		}

		ofs = security_offset + security_buffer.length;
		if ((ofs % 8) != 0) {
			pad = 8 - (ofs % 8);
		}
		ofs += pad;

		ok = data_blob_append(req, &outdyn, zeros, pad);
		if (!ok) {
			return smbd_smb2_request_error(req,
						NT_STATUS_NO_MEMORY);
		}

		ok = data_blob_append(req, &outdyn,
				      out_negotiate_context_blob.data,
				      out_negotiate_context_blob.length);
		if (!ok) {
			return smbd_smb2_request_error(req,
						NT_STATUS_NO_MEMORY);
		}

		out_negotiate_context_offset = ofs;
		out_negotiate_context_count = out_c.num_contexts;
	} else {
		outdyn = security_buffer;
	}

	out_guid_blob = data_blob_const(negprot_spnego_blob.data, 16);
	status = GUID_from_ndr_blob(&out_guid_blob, &out_guid);
	if (!NT_STATUS_IS_OK(status)) {
		return smbd_smb2_request_error(req, status);
	}

	outbody = smbd_smb2_generate_outbody(req, 0x40);
	if (outbody.data == NULL) {
		return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
	}

	SSVAL(outbody.data, 0x00, 0x40 + 1);	/* struct size */
	SSVAL(outbody.data, 0x02,
	      security_mode);			/* security mode */
	SSVAL(outbody.data, 0x04, dialect);	/* dialect revision */
	SSVAL(outbody.data, 0x06,
	      out_negotiate_context_count);	/* reserved/NegotiateContextCount */
	memcpy(outbody.data + 0x08,
	       out_guid_blob.data, 16);	/* server guid */
	SIVAL(outbody.data, 0x18,
	      capabilities);			/* capabilities */
	SIVAL(outbody.data, 0x1C, max_trans);	/* max transact size */
	SIVAL(outbody.data, 0x20, max_read);	/* max read size */
	SIVAL(outbody.data, 0x24, max_write);	/* max write size */
	SBVAL(outbody.data, 0x28, now);		/* system time */
	SBVAL(outbody.data, 0x30, 0);		/* server start time */
	SSVAL(outbody.data, 0x38,
	      security_offset);			/* security buffer offset */
	SSVAL(outbody.data, 0x3A,
	      security_buffer.length);		/* security buffer length */
	SIVAL(outbody.data, 0x3C,
	      out_negotiate_context_offset);	/* reserved/NegotiateContextOffset */

	req->sconn->using_smb2 = true;

	if (dialect == SMB2_DIALECT_REVISION_2FF) {
		return smbd_smb2_request_done(req, outbody, &outdyn);
	}

	status = smbXsrv_connection_init_tables(xconn, protocol);
	if (!NT_STATUS_IS_OK(status)) {
		return smbd_smb2_request_error(req, status);
	}

	xconn->smb2.client.capabilities = in_capabilities;
	xconn->smb2.client.security_mode = in_security_mode;
	xconn->smb2.client.guid = in_guid;
	xconn->smb2.client.num_dialects = dialect_count;
	xconn->smb2.client.dialects = talloc_array(xconn,
						   uint16_t,
						   dialect_count);
	if (xconn->smb2.client.dialects == NULL) {
		return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
	}
	for (c=0; c < dialect_count; c++) {
		xconn->smb2.client.dialects[c] = SVAL(indyn, c*2);
	}

	xconn->smb2.server.capabilities = capabilities;
	xconn->smb2.server.security_mode = security_mode;
	xconn->smb2.server.guid = out_guid;
	xconn->smb2.server.dialect = dialect;
	xconn->smb2.server.max_trans = max_trans;
	xconn->smb2.server.max_read  = max_read;
	xconn->smb2.server.max_write = max_write;

	if (xconn->protocol < PROTOCOL_SMB2_10) {
		/*
		 * SMB2_02 doesn't support client guids
		 */
		return smbd_smb2_request_done(req, outbody, &outdyn);
	}

	if (!xconn->client->server_multi_channel_enabled) {
		/*
		 * Only deal with the client guid database
		 * if multi-channel is enabled.
		 *
		 * But we still need to setup
		 * xconn->client->global->client_guid to
		 * the correct value.
		 */
		xconn->client->global->client_guid =
			xconn->smb2.client.guid;
		return smbd_smb2_request_done(req, outbody, &outdyn);
	}

	if (xconn->smb2.client.guid_verified) {
		/*
		 * The connection was passed from another
		 * smbd process.
		 */
		return smbd_smb2_request_done(req, outbody, &outdyn);
	}

	state = talloc_zero(req, struct smbd_smb2_request_process_negprot_state);
	if (state == NULL) {
		return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
	}
	*state = (struct smbd_smb2_request_process_negprot_state) {
		.req = req,
		.outbody = outbody,
		.outdyn = outdyn,
	};

	subreq = smb2srv_client_mc_negprot_send(state,
						req->xconn->client->raw_ev_ctx,
						req);
	if (subreq == NULL) {
		return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
	}
	tevent_req_set_callback(subreq,
				smbd_smb2_request_process_negprot_mc_done,
				state);
	return NT_STATUS_OK;
}

static void smbd_smb2_request_process_negprot_mc_done(struct tevent_req *subreq)
{
	struct smbd_smb2_request_process_negprot_state *state =
		tevent_req_callback_data(subreq,
		struct smbd_smb2_request_process_negprot_state);
	struct smbd_smb2_request *req = state->req;
	struct smbXsrv_connection *xconn = req->xconn;
	NTSTATUS status;

	status = smb2srv_client_mc_negprot_recv(subreq);
	TALLOC_FREE(subreq);
	if (NT_STATUS_EQUAL(status, NT_STATUS_MESSAGE_RETRIEVED)) {
		/*
		 * The connection was passed to another process
		 */
		smbd_server_connection_terminate(xconn,
						 "passed connection");
		/*
		 * smbd_server_connection_terminate() should not return!
		 */
		smb_panic(__location__);
		return;
	}
	if (!NT_STATUS_IS_OK(status)) {
		status = smbd_smb2_request_error(req, status);
		if (NT_STATUS_IS_OK(status)) {
			return;
		}

		/*
		 * The connection was passed to another process
		 */
		smbd_server_connection_terminate(xconn, nt_errstr(status));
		/*
		 * smbd_server_connection_terminate() should not return!
		 */
		smb_panic(__location__);
		return;
	}

	/*
	 * We're the first connection...
	 */
	status = smbd_smb2_request_done(req, state->outbody, &state->outdyn);
	if (NT_STATUS_IS_OK(status)) {
		return;
	}

	/*
	 * The connection was passed to another process
	 */
	smbd_server_connection_terminate(xconn, nt_errstr(status));
	/*
	 * smbd_server_connection_terminate() should not return!
	 */
	smb_panic(__location__);
	return;
}

/****************************************************************************
 Generate the spnego negprot reply blob. Return the number of bytes used.
****************************************************************************/

DATA_BLOB negprot_spnego(TALLOC_CTX *ctx, struct smbXsrv_connection *xconn)
{
	DATA_BLOB blob = data_blob_null;
	DATA_BLOB blob_out = data_blob_null;
	nstring dos_name;
	fstring unix_name;
	NTSTATUS status;
#ifdef DEVELOPER
	size_t slen;
#endif
	struct gensec_security *gensec_security;

	/* See if we can get an SPNEGO blob */
	status = auth_generic_prepare(talloc_tos(),
				      xconn->remote_address,
				      xconn->local_address,
				      "SMB",
				      &gensec_security);

	/*
	 * Despite including it above, there is no need to set a
	 * remote address or similar as we are just interested in the
	 * SPNEGO blob, we never keep this context.
	 */

	if (NT_STATUS_IS_OK(status)) {
		status = gensec_start_mech_by_oid(gensec_security, GENSEC_OID_SPNEGO);
		if (NT_STATUS_IS_OK(status)) {
			status = gensec_update(gensec_security, ctx,
					       data_blob_null, &blob);
			/* If we get the list of OIDs, the 'OK' answer
			 * is NT_STATUS_MORE_PROCESSING_REQUIRED */
			if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
				DEBUG(0, ("Failed to start SPNEGO handler for negprot OID list!\n"));
				blob = data_blob_null;
			}
		}
		TALLOC_FREE(gensec_security);
	}

#if defined(WITH_SMB1SERVER)
	xconn->smb1.negprot.spnego = true;
#endif

	/* strangely enough, NT does not sent the single OID NTLMSSP when
	   not a ADS member, it sends no OIDs at all

	   OLD COMMENT : "we can't do this until we teach our sesssion setup parser to know
		   about raw NTLMSSP (clients send no ASN.1 wrapping if we do this)"

	   Our sessionsetup code now handles raw NTLMSSP connects, so we can go
	   back to doing what W2K3 does here. This is needed to make PocketPC 2003
	   CIFS connections work with SPNEGO. See bugzilla bugs #1828 and #3133
	   for details. JRA.

	*/

	if (blob.length == 0 || blob.data == NULL) {
		return data_blob_null;
	}

	blob_out = data_blob_talloc(ctx, NULL, 16 + blob.length);
	if (blob_out.data == NULL) {
		data_blob_free(&blob);
		return data_blob_null;
	}

	memset(blob_out.data, '\0', 16);

	checked_strlcpy(unix_name, lp_netbios_name(), sizeof(unix_name));
	(void)strlower_m(unix_name);
	push_ascii_nstring(dos_name, unix_name);
	strlcpy((char *)blob_out.data, dos_name, 17);

#ifdef DEVELOPER
	/* Fix valgrind 'uninitialized bytes' issue. */
	slen = strlen(dos_name);
	if (slen < 16) {
		memset(blob_out.data+slen, '\0', 16 - slen);
	}
#endif

	memcpy(&blob_out.data[16], blob.data, blob.length);

	data_blob_free(&blob);

	return blob_out;
}

/*
 * MS-CIFS, 2.2.4.52.2 SMB_COM_NEGOTIATE Response:
 * If the server does not support any of the listed dialects, it MUST return a
 * DialectIndex of 0XFFFF
 */
#define NO_PROTOCOL_CHOSEN	0xffff

#define PROT_SMB_2_002				0x1000
#define PROT_SMB_2_FF				0x2000

/* List of supported SMB1 protocols, most desired first.
 * This is for enabling multi-protocol negotiation in SMB2 when SMB1
 * is disabled.
 */
static const struct {
	const char *proto_name;
	const char *short_name;
	NTSTATUS (*proto_reply_fn)(struct smb_request *req, uint16_t choice);
	int protocol_level;
} supported_protocols[] = {
	{"SMB 2.???",               "SMB2_FF",  reply_smb20ff,  PROTOCOL_SMB2_10},
	{"SMB 2.002",               "SMB2_02",  reply_smb2002,  PROTOCOL_SMB2_02},
	{NULL,NULL,NULL,0},
};

/****************************************************************************
 Reply to a negprot.
 conn POINTER CAN BE NULL HERE !
****************************************************************************/

NTSTATUS smb2_multi_protocol_reply_negprot(struct smb_request *req)
{
	size_t choice = 0;
	bool choice_set = false;
	int protocol;
	const char *p;
	int num_cliprotos;
	char **cliprotos;
	size_t i;
	size_t converted_size;
	struct smbXsrv_connection *xconn = req->xconn;
	struct smbd_server_connection *sconn = req->sconn;
	int max_proto;
	int min_proto;
	NTSTATUS status;

	START_PROFILE(SMBnegprot);

	if (req->buflen == 0) {
		DEBUG(0, ("negprot got no protocols\n"));
		reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
		END_PROFILE(SMBnegprot);
		return NT_STATUS_INVALID_PARAMETER;
	}

	if (req->buf[req->buflen-1] != '\0') {
		DEBUG(0, ("negprot protocols not 0-terminated\n"));
		reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
		END_PROFILE(SMBnegprot);
		return NT_STATUS_INVALID_PARAMETER;
	}

	p = (const char *)req->buf + 1;

	num_cliprotos = 0;
	cliprotos = NULL;

	while (smbreq_bufrem(req, p) > 0) {

		char **tmp;

		tmp = talloc_realloc(talloc_tos(), cliprotos, char *,
					   num_cliprotos+1);
		if (tmp == NULL) {
			DEBUG(0, ("talloc failed\n"));
			TALLOC_FREE(cliprotos);
			reply_nterror(req, NT_STATUS_NO_MEMORY);
			END_PROFILE(SMBnegprot);
			return NT_STATUS_NO_MEMORY;
		}

		cliprotos = tmp;

		if (!pull_ascii_talloc(cliprotos, &cliprotos[num_cliprotos], p,
				       &converted_size)) {
			DEBUG(0, ("pull_ascii_talloc failed\n"));
			TALLOC_FREE(cliprotos);
			reply_nterror(req, NT_STATUS_NO_MEMORY);
			END_PROFILE(SMBnegprot);
			return NT_STATUS_NO_MEMORY;
		}

		DEBUG(3, ("Requested protocol [%s]\n",
			  cliprotos[num_cliprotos]));

		num_cliprotos += 1;
		p += strlen(p) + 2;
	}

	/* possibly reload - change of architecture */
	reload_services(sconn, conn_snum_used, true);

	/*
	 * Anything higher than PROTOCOL_SMB2_10 still
	 * needs to go via "SMB 2.???", which is marked
	 * as PROTOCOL_SMB2_10.
	 *
	 * The real negotiation happens via reply_smb20ff()
	 * using SMB2 Negotiation.
	 */
	max_proto = lp_server_max_protocol();
	if (max_proto > PROTOCOL_SMB2_10) {
		max_proto = PROTOCOL_SMB2_10;
	}
	min_proto = lp_server_min_protocol();
	if (min_proto > PROTOCOL_SMB2_10) {
		min_proto = PROTOCOL_SMB2_10;
	}

	/* Check for protocols, most desirable first */
	for (protocol = 0; supported_protocols[protocol].proto_name; protocol++) {
		i = 0;
		if ((supported_protocols[protocol].protocol_level <= max_proto) &&
		    (supported_protocols[protocol].protocol_level >= min_proto))
			while (i < num_cliprotos) {
				if (strequal(cliprotos[i],supported_protocols[protocol].proto_name)) {
					choice = i;
					choice_set = true;
				}
				i++;
			}
		if (choice_set) {
			break;
		}
	}

	if (!choice_set) {
		bool ok;

		DBG_NOTICE("No protocol supported !\n");
		reply_smb1_outbuf(req, 1, 0);
		SSVAL(req->outbuf, smb_vwv0, NO_PROTOCOL_CHOSEN);

		ok = smb1_srv_send(xconn, (char *)req->outbuf,
				  false, 0, false, NULL);
		if (!ok) {
			DBG_NOTICE("smb1_srv_send failed\n");
		}
		exit_server_cleanly("no protocol supported\n");
	}

	fstrcpy(remote_proto,supported_protocols[protocol].short_name);
	reload_services(sconn, conn_snum_used, true);
	status = supported_protocols[protocol].proto_reply_fn(req, choice);
	if (!NT_STATUS_IS_OK(status)) {
		exit_server_cleanly("negprot function failed\n");
	}

	DEBUG(3,("Selected protocol %s\n",supported_protocols[protocol].proto_name));

	DBG_INFO("negprot index=%zu\n", choice);

	TALLOC_FREE(cliprotos);

	END_PROFILE(SMBnegprot);
	return NT_STATUS_OK;
}
