[Patches] The way to remove gensec_update_ev()

Stefan Metzmacher metze at samba.org
Sun Jul 2 20:14:41 UTC 2017


Hi Andrew,

>>>>> here're some preparation patches which passed autobuild twice.
>>>
>>> and some more just passed a private autobuild...
>>>
>>> Please review and push:-)
>>
>> These are in master now.
>>
>> Here's the next chunk, please review and push:-)
> 
> Reviewed-by: Andrew Bartlett <abartlet at samba.org>

https://git.samba.org/?p=metze/samba/wip.git;a=shortlog;h=refs/heads/master3-gensec-tmp
(it makes spnego.c fully async) passed private autobuilds a few times now.

I'll try to it up into useful commits tomorrow.

It would be good if you could start reviewing the final spnego.c
(attached)

https://git.samba.org/?p=metze/samba/wip.git;a=shortlog;h=refs/heads/master3-gensec
(implements the async NTLMSSP server with basic support for trusts) also
seems to pass, but I need to fix up some things there.

metze

-------------- next part --------------
/* 
   Unix SMB/CIFS implementation.

   RFC2478 Compliant SPNEGO implementation

   Copyright (C) Jim McDonough <jmcd at us.ibm.com>      2003
   Copyright (C) Andrew Bartlett <abartlet at samba.org> 2004-2005
   Copyright (C) Stefan Metzmacher <metze at samba.org>  2004-2008

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

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


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

#include "includes.h"
#include <tevent.h>
#include "lib/util/tevent_ntstatus.h"
#include "../libcli/auth/spnego.h"
#include "librpc/gen_ndr/ndr_dcerpc.h"
#include "auth/credentials/credentials.h"
#include "auth/gensec/gensec.h"
#include "auth/gensec/gensec_internal.h"
#include "param/param.h"
#include "lib/util/asn1.h"
#include "lib/util/base64.h"

#undef strcasecmp

struct spnego_state;

_PUBLIC_ NTSTATUS gensec_spnego_init(TALLOC_CTX *ctx);

static NTSTATUS gensec_spnego_update_pre_sub(struct gensec_security *gensec_security,
					     struct spnego_data *spnego);
static NTSTATUS gensec_spnego_update_post_sub(struct gensec_security *gensec_security,
					      struct spnego_data *spnego,
					      TALLOC_CTX *mem_ctx,
					      DATA_BLOB *mech_list_mic);

static NTSTATUS gensec_spnego_server_response(struct spnego_state *spnego_state,
					      TALLOC_CTX *out_mem_ctx,
					      NTSTATUS nt_status,
					      const DATA_BLOB unwrapped_out,
					      DATA_BLOB mech_list_mic,
					      DATA_BLOB *out);

enum spnego_state_position {
	SPNEGO_SERVER_START,
	SPNEGO_CLIENT_START,
	SPNEGO_SERVER_TARG,
	SPNEGO_CLIENT_TARG,
	SPNEGO_FALLBACK,
	SPNEGO_DONE
};

struct spnego_neg_ops;

struct spnego_neg_state {
	const struct spnego_neg_ops *ops;
	const struct gensec_security_ops_wrapper *all_sec;
	size_t all_idx;
	const char * const *mech_types;
	size_t mech_idx;
};

struct spnego_neg_ops {
	const char *name;
	NTSTATUS (*start_fn)(struct gensec_security *gensec_security,
			     struct spnego_state *spnego_state,
			     struct spnego_neg_state *n,
			     struct spnego_data *spnego_in,
			     TALLOC_CTX *in_mem_ctx,
			     DATA_BLOB *in_next);
	NTSTATUS (*step_fn)(struct gensec_security *gensec_security,
			    struct spnego_state *spnego_state,
			    struct spnego_neg_state *n,
			    struct spnego_data *spnego_in,
			    NTSTATUS last_status,
			    TALLOC_CTX *in_mem_ctx,
			    DATA_BLOB *in_next);
	NTSTATUS (*finish_fn)(struct gensec_security *gensec_security,
			      struct spnego_state *spnego_state,
			      struct spnego_neg_state *n,
			      struct spnego_data *spnego_in,
			      NTSTATUS sub_status,
			      const DATA_BLOB sub_out,
			      TALLOC_CTX *out_mem_ctx,
			      DATA_BLOB *out);
};

struct spnego_state {
	enum spnego_message_type expected_packet;
	enum spnego_state_position state_position;
	struct gensec_security *sub_sec_security;
	bool sub_sec_ready;

	const char *neg_oid;

	DATA_BLOB mech_types;
	size_t num_targs;
	bool downgraded;
	bool mic_requested;
	bool prepared_mic_check;
	bool needs_mic_sign;
	bool needs_mic_check;
	bool may_skip_mic_check;
	bool done_mic_check;

	bool simulate_w2k;

	/*
	 * The following is used to implement
	 * the update token fragmentation
	 */
	size_t in_needed;
	DATA_BLOB in_frag;
	size_t out_max_length;
	DATA_BLOB out_frag;
	NTSTATUS out_status;
};

static struct spnego_neg_state *gensec_spnego_neg_state(TALLOC_CTX *mem_ctx,
				       const const struct spnego_neg_ops *ops)
{
	struct spnego_neg_state *n = NULL;

	n = talloc_zero(mem_ctx, struct spnego_neg_state);
	if (n == NULL) {
		return NULL;
	}
	n->ops = ops;

	return n;
}

static void gensec_spnego_update_sub_abort(struct spnego_state *spnego_state)
{
	spnego_state->sub_sec_ready = false;
	TALLOC_FREE(spnego_state->sub_sec_security);
}

static NTSTATUS gensec_spnego_client_start(struct gensec_security *gensec_security)
{
	struct spnego_state *spnego_state;

	spnego_state = talloc_zero(gensec_security, struct spnego_state);
	if (!spnego_state) {
		return NT_STATUS_NO_MEMORY;
	}

	spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT;
	spnego_state->state_position = SPNEGO_CLIENT_START;
	spnego_state->sub_sec_security = NULL;
	spnego_state->sub_sec_ready = false;
	spnego_state->mech_types = data_blob_null;
	spnego_state->out_max_length = gensec_max_update_size(gensec_security);
	spnego_state->out_status = NT_STATUS_MORE_PROCESSING_REQUIRED;

	spnego_state->simulate_w2k = gensec_setting_bool(gensec_security->settings,
						"spnego", "simulate_w2k", false);

	gensec_security->private_data = spnego_state;
	return NT_STATUS_OK;
}

static NTSTATUS gensec_spnego_server_start(struct gensec_security *gensec_security)
{
	struct spnego_state *spnego_state;

	spnego_state = talloc_zero(gensec_security, struct spnego_state);
	if (!spnego_state) {
		return NT_STATUS_NO_MEMORY;
	}

	spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT;
	spnego_state->state_position = SPNEGO_SERVER_START;
	spnego_state->sub_sec_security = NULL;
	spnego_state->sub_sec_ready = false;
	spnego_state->mech_types = data_blob_null;
	spnego_state->out_max_length = gensec_max_update_size(gensec_security);
	spnego_state->out_status = NT_STATUS_MORE_PROCESSING_REQUIRED;

	spnego_state->simulate_w2k = gensec_setting_bool(gensec_security->settings,
						"spnego", "simulate_w2k", false);

	gensec_security->private_data = spnego_state;
	return NT_STATUS_OK;
}

/** Fallback to another GENSEC mechanism, based on magic strings 
 *
 * This is the 'fallback' case, where we don't get SPNEGO, and have to
 * try all the other options (and hope they all have a magic string
 * they check)
*/

static NTSTATUS gensec_spnego_server_try_fallback(struct gensec_security *gensec_security, 
						  struct spnego_state *spnego_state,
						  TALLOC_CTX *mem_ctx,
						  const DATA_BLOB in)
{
	int i,j;
	const struct gensec_security_ops **all_ops;

	all_ops = gensec_security_mechs(gensec_security, mem_ctx);

	for (i=0; all_ops && all_ops[i]; i++) {
		bool is_spnego;
		NTSTATUS nt_status;

	    	if (gensec_security != NULL && 
				!gensec_security_ops_enabled(all_ops[i], gensec_security))
		    continue;

		if (!all_ops[i]->oid) {
			continue;
		}

		is_spnego = false;
		for (j=0; all_ops[i]->oid[j]; j++) {
			if (strcasecmp(GENSEC_OID_SPNEGO,all_ops[i]->oid[j]) == 0) {
				is_spnego = true;
			}
		}
		if (is_spnego) {
			continue;
		}

		if (!all_ops[i]->magic) {
			continue;
		}

		nt_status = all_ops[i]->magic(gensec_security, &in);
		if (!NT_STATUS_IS_OK(nt_status)) {
			continue;
		}

		spnego_state->state_position = SPNEGO_FALLBACK;

		nt_status = gensec_subcontext_start(spnego_state, 
						    gensec_security, 
						    &spnego_state->sub_sec_security);

		if (!NT_STATUS_IS_OK(nt_status)) {
			return nt_status;
		}
		/* select the sub context */
		nt_status = gensec_start_mech_by_ops(spnego_state->sub_sec_security,
						     all_ops[i]);
		if (!NT_STATUS_IS_OK(nt_status)) {
			return nt_status;
		}

		return NT_STATUS_OK;
	}
	DEBUG(1, ("Failed to parse SPNEGO request\n"));
	return NT_STATUS_INVALID_PARAMETER;
}

static NTSTATUS gensec_spnego_client_negTokenInit_start(
					struct gensec_security *gensec_security,
					struct spnego_state *spnego_state,
					struct spnego_neg_state *n,
					struct spnego_data *spnego_in,
					TALLOC_CTX *in_mem_ctx,
					DATA_BLOB *in_next)
{
	const char *tp = NULL;
	NTSTATUS status;
	bool ok;

	if (spnego_in->type != SPNEGO_NEG_TOKEN_INIT) {
		return NT_STATUS_INTERNAL_ERROR;
	}

	n->mech_idx = 0;
	n->mech_types = spnego_in->negTokenInit.mechTypes;
	if (n->mech_types == NULL) {
		return NT_STATUS_INVALID_PARAMETER;
	}

	n->all_idx = 0;
	n->all_sec = gensec_security_by_oid_list(gensec_security,
						 n, n->mech_types,
						 GENSEC_OID_SPNEGO);
	if (n->all_sec == NULL) {
		status = NT_STATUS_INVALID_PARAMETER;
		DBG_DEBUG("Failed to setup SPNEGO negTokenInit request: "
			  "%s\n", nt_errstr(status));
		return status;
	}

	ok = spnego_write_mech_types(spnego_state,
				     n->mech_types,
				     &spnego_state->mech_types);
	if (!ok) {
		DEBUG(1, ("SPNEGO: Failed to write mechTypes\n"));
		return NT_STATUS_NO_MEMORY;
	}

	tp = spnego_in->negTokenInit.targetPrincipal;
	if (tp != NULL && strcmp(tp, ADS_IGNORE_PRINCIPAL) != 0) {
		DEBUG(5, ("Server claims it's principal name is %s\n", tp));
		if (lpcfg_client_use_spnego_principal(gensec_security->settings->lp_ctx)) {
			gensec_set_target_principal(gensec_security, tp);
		}
	}

	/*
	 * As a client we ignore the servers mech_token.
	 */
	spnego_in->negTokenInit.mechToken = data_blob_null;

	return n->ops->step_fn(gensec_security, spnego_state, n,
			       spnego_in, NT_STATUS_OK, in_mem_ctx, in_next);
}

static NTSTATUS gensec_spnego_client_negTokenInit_step(
					struct gensec_security *gensec_security,
					struct spnego_state *spnego_state,
					struct spnego_neg_state *n,
					struct spnego_data *spnego_in,
					NTSTATUS last_status,
					TALLOC_CTX *in_mem_ctx,
					DATA_BLOB *in_next)
{
	bool allow_fallback = false;

	if (NT_STATUS_EQUAL(last_status, NT_STATUS_INVALID_PARAMETER) ||
	    NT_STATUS_EQUAL(last_status, NT_STATUS_NO_LOGON_SERVERS) ||
	    NT_STATUS_EQUAL(last_status, NT_STATUS_TIME_DIFFERENCE_AT_DC) ||
	    NT_STATUS_EQUAL(last_status, NT_STATUS_CANT_ACCESS_DOMAIN_INFO))
	{
		allow_fallback = true;
	}

	if (!NT_STATUS_IS_OK(last_status)) {
		const struct gensec_security_ops_wrapper *cur_sec =
			&n->all_sec[n->all_idx];
		const char *next = NULL;
		const char *principal = NULL;
		int dbg_level = DBGLVL_WARNING;

		if (allow_fallback && cur_sec[1].op != NULL) {
			next = cur_sec[1].op->name;
			dbg_level = DBGLVL_NOTICE;
		}

		if (gensec_security->target.principal != NULL) {
			principal = gensec_security->target.principal;
		} else if (gensec_security->target.service != NULL &&
			   gensec_security->target.hostname != NULL)
		{
			principal = talloc_asprintf(spnego_state->sub_sec_security,
						    "%s/%s",
						    gensec_security->target.service,
						    gensec_security->target.hostname);
		} else {
			principal = gensec_security->target.hostname;
		}

		DBG_PREFIX(dbg_level,
			   ("SPNEGO(%s) creating NEG_TOKEN_INIT "
			    "for %s failed (next[%s]): %s\n",
			    spnego_state->sub_sec_security->ops->name,
			    principal, next, nt_errstr(last_status)));

		if (!allow_fallback) {
			return last_status;
		}

		/*
		 * Pretend we never started it
		 */
		data_blob_free(&spnego_state->mech_types);
		gensec_spnego_update_sub_abort(spnego_state);

		/*
		 * And try the next one...
		 */
		n->all_idx += 1;
	}

	if (NT_STATUS_IS_OK(last_status)) {
		/*
		 * Use a resonable default in case we just started,
		 * This is the case where we're called from
		 * gensec_spnego_client_negTokenInit_prepare().
		 */
		last_status = NT_STATUS_INVALID_PARAMETER;
	}

	for (; n->all_sec[n->all_idx].op != NULL; n->all_idx++) {
		const struct gensec_security_ops_wrapper *cur_sec =
			&n->all_sec[n->all_idx];
		NTSTATUS status;
		bool ok;

		data_blob_free(&spnego_state->mech_types);
		gensec_spnego_update_sub_abort(spnego_state);

		status = gensec_subcontext_start(spnego_state,
						 gensec_security,
						 &spnego_state->sub_sec_security);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}
		/* select the sub context */
		status = gensec_start_mech_by_ops(spnego_state->sub_sec_security,
						  cur_sec->op);
		if (!NT_STATUS_IS_OK(status)) {
			gensec_spnego_update_sub_abort(spnego_state);
			continue;
		}

		n->mech_types = gensec_security_oids_from_ops_wrapped(n, cur_sec);
		if (n->mech_types == NULL) {
			DBG_ERR("SPNEGO: Failed to generate send_mech_types\n");
			return NT_STATUS_NO_MEMORY;
		}

		ok = spnego_write_mech_types(spnego_state,
					     n->mech_types,
					     &spnego_state->mech_types);
		if (!ok) {
			DBG_ERR("SPNEGO: Failed to write mechTypes\n");
			return NT_STATUS_NO_MEMORY;
		}

		/* In the client, try and produce the first (optimistic) packet */
		return NT_STATUS_MORE_PROCESSING_REQUIRED;
	}

	DBG_WARNING("SPNEGO: Could not find a suitable mechtype in "
		    "NEG_TOKEN_INIT, %s\n", nt_errstr(last_status));
	return last_status;
}

static NTSTATUS gensec_spnego_client_negTokenInit_finish(
					struct gensec_security *gensec_security,
					struct spnego_state *spnego_state,
					struct spnego_neg_state *n,
					struct spnego_data *spnego_in,
					NTSTATUS sub_status,
					const DATA_BLOB sub_out,
					TALLOC_CTX *out_mem_ctx,
					DATA_BLOB *out)
{
	const struct gensec_security_ops_wrapper *cur_sec =
			&n->all_sec[n->all_idx];
	struct spnego_data spnego_out;

	spnego_out.type = SPNEGO_NEG_TOKEN_INIT;

	/* List the remaining mechs as options */
	spnego_out.negTokenInit.mechTypes = n->mech_types;
	spnego_out.negTokenInit.reqFlags = data_blob_null;
	spnego_out.negTokenInit.reqFlagsPadding = 0;
	spnego_out.negTokenInit.mechListMIC = data_blob_null;
	spnego_out.negTokenInit.mechToken = sub_out;

	if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
		DEBUG(1, ("Failed to write NEG_TOKEN_INIT\n"));
		return NT_STATUS_INVALID_PARAMETER;
	}

	/*
	 * Note that 'cur_sec' is temporary memory, but
	 * cur_sec->oid points to a const string in the
	 * backends gensec_security_ops structure.
	 */
	spnego_state->neg_oid = cur_sec->oid;

	/* set next state */
	spnego_state->state_position = SPNEGO_CLIENT_TARG;
	spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;

	return NT_STATUS_MORE_PROCESSING_REQUIRED;
}

static const struct spnego_neg_ops gensec_spnego_client_negTokenInit_ops = {
	.name      = "client_negTokenInit",
	.start_fn  = gensec_spnego_client_negTokenInit_start,
	.step_fn   = gensec_spnego_client_negTokenInit_step,
	.finish_fn = gensec_spnego_client_negTokenInit_finish,
};

static NTSTATUS gensec_spnego_client_negTokenTarg_start(
					struct gensec_security *gensec_security,
					struct spnego_state *spnego_state,
					struct spnego_neg_state *n,
					struct spnego_data *spnego_in,
					TALLOC_CTX *in_mem_ctx,
					DATA_BLOB *in_next)
{
	const struct spnego_negTokenTarg *ta =
		&spnego_in->negTokenTarg;
	NTSTATUS status;

	if (spnego_in->type != SPNEGO_NEG_TOKEN_TARG) {
		return NT_STATUS_INTERNAL_ERROR;
	}

	if (spnego_state->sub_sec_security == NULL) {
		DBG_ERR("SPNEGO: Did not setup a mech in NEG_TOKEN_INIT\n");
		return NT_STATUS_INTERNAL_ERROR;
	}

	spnego_state->num_targs++;

	if (ta->negResult == SPNEGO_REJECT) {
		return NT_STATUS_LOGON_FAILURE;
	}

	/* Server didn't like our choice of mech, and chose something else */
	if (((ta->negResult == SPNEGO_ACCEPT_INCOMPLETE) ||
	     (ta->negResult == SPNEGO_REQUEST_MIC)) &&
	    ta->supportedMech != NULL &&
	    strcmp(ta->supportedMech, spnego_state->neg_oid) != 0) {

		DBG_NOTICE("GENSEC SPNEGO: client preferred mech (%s) not accepted, "
			   "server wants: %s\n",
			   gensec_get_name_by_oid(gensec_security,
						  spnego_state->neg_oid),
			   gensec_get_name_by_oid(gensec_security,
						  ta->supportedMech));

		spnego_state->downgraded = true;

		gensec_spnego_update_sub_abort(spnego_state);

		status = gensec_subcontext_start(spnego_state,
						 gensec_security,
						 &spnego_state->sub_sec_security);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}
		/* select the sub context */
		status = gensec_start_mech_by_oid(spnego_state->sub_sec_security,
						  ta->supportedMech);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}

		spnego_state->neg_oid = talloc_strdup(spnego_state,
					ta->supportedMech);
		if (spnego_state->neg_oid == NULL) {
			return NT_STATUS_NO_MEMORY;
		}
	}

	status = gensec_spnego_update_pre_sub(gensec_security,
					      spnego_in);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	if (!spnego_state->sub_sec_ready) {
		*in_next = spnego_in->negTokenTarg.responseToken;
		return NT_STATUS_MORE_PROCESSING_REQUIRED;
	}

	return NT_STATUS_OK;
}

static NTSTATUS gensec_spnego_client_negTokenTarg_step(
					struct gensec_security *gensec_security,
					struct spnego_state *spnego_state,
					struct spnego_neg_state *n,
					struct spnego_data *spnego_in,
					NTSTATUS last_status,
					TALLOC_CTX *in_mem_ctx,
					DATA_BLOB *in_next)
{
	if (GENSEC_UPDATE_IS_NTERROR(last_status)) {
		return last_status;
	}

	return NT_STATUS_OK;
}

static NTSTATUS gensec_spnego_client_negTokenTarg_finish(
					struct gensec_security *gensec_security,
					struct spnego_state *spnego_state,
					struct spnego_neg_state *n,
					struct spnego_data *spnego_in,
					NTSTATUS sub_status,
					const DATA_BLOB sub_out,
					TALLOC_CTX *out_mem_ctx,
					DATA_BLOB *out)
{
	const struct spnego_negTokenTarg *ta =
		&spnego_in->negTokenTarg;
	DATA_BLOB mech_list_mic = data_blob_null;
	struct spnego_data spnego_out;

	if (spnego_state->sub_sec_ready) {
		NTSTATUS status;

		status = gensec_spnego_update_post_sub(gensec_security,
						       spnego_in, n,
						       &mech_list_mic);
		if (GENSEC_UPDATE_IS_NTERROR(status)) {
			return status;
		}
	}

	if (sub_out.length == 0 && mech_list_mic.length == 0) {
		*out = data_blob_null;

		if (!spnego_state->sub_sec_ready) {
			/* somethings wrong here... */
			DBG_ERR("gensec_update more without output\n");
			return NT_STATUS_INTERNAL_ERROR;
		}

		if (ta->negResult != SPNEGO_ACCEPT_COMPLETED) {
			/* unless of course it did not accept */
			DBG_WARNING("gensec_update ok but not accepted\n");
			return NT_STATUS_INVALID_PARAMETER;
		}

		spnego_state->state_position = SPNEGO_DONE;
		return NT_STATUS_OK;
	}

	/* compose reply */
	spnego_out.type = SPNEGO_NEG_TOKEN_TARG;
	spnego_out.negTokenTarg.negResult = SPNEGO_NONE_RESULT;
	spnego_out.negTokenTarg.supportedMech = NULL;
	spnego_out.negTokenTarg.responseToken = sub_out;
	spnego_out.negTokenTarg.mechListMIC = mech_list_mic;

	if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
		DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_TARG\n"));
		return NT_STATUS_INVALID_PARAMETER;
	}

	spnego_state->num_targs++;

	/* set next state */
	spnego_state->state_position = SPNEGO_CLIENT_TARG;
	spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;

	return NT_STATUS_MORE_PROCESSING_REQUIRED;
}

static const struct spnego_neg_ops gensec_spnego_client_negTokenTarg_ops = {
	.name      = "client_negTokenTarg",
	.start_fn  = gensec_spnego_client_negTokenTarg_start,
	.step_fn   = gensec_spnego_client_negTokenTarg_step,
	.finish_fn = gensec_spnego_client_negTokenTarg_finish,
};

static NTSTATUS gensec_spnego_server_negTokenInit_start(
					struct gensec_security *gensec_security,
					struct spnego_state *spnego_state,
					struct spnego_neg_state *n,
					struct spnego_data *spnego_in,
					TALLOC_CTX *in_mem_ctx,
					DATA_BLOB *in_next)
{
	NTSTATUS status;
	bool ok;

	if (spnego_in->type != SPNEGO_NEG_TOKEN_INIT) {
		return NT_STATUS_INTERNAL_ERROR;
	}

	n->mech_idx = 0;
	n->mech_types = spnego_in->negTokenInit.mechTypes;
	if (n->mech_types == NULL) {
		return NT_STATUS_INVALID_PARAMETER;
	}

	n->all_idx = 0;
	n->all_sec = gensec_security_by_oid_list(gensec_security,
						 n, n->mech_types,
						 GENSEC_OID_SPNEGO);
	if (n->all_sec == NULL) {
		status = NT_STATUS_INVALID_PARAMETER;
		DBG_DEBUG("Failed to setup SPNEGO negTokenInit request: "
			  "%s\n", nt_errstr(status));
		return status;
	}

	ok = spnego_write_mech_types(spnego_state,
				     n->mech_types,
				     &spnego_state->mech_types);
	if (!ok) {
		DEBUG(1, ("SPNEGO: Failed to write mechTypes\n"));
		return NT_STATUS_NO_MEMORY;
	}

	return n->ops->step_fn(gensec_security, spnego_state, n,
			       spnego_in, NT_STATUS_OK, in_mem_ctx, in_next);
}

static NTSTATUS gensec_spnego_server_negTokenInit_step(
					struct gensec_security *gensec_security,
					struct spnego_state *spnego_state,
					struct spnego_neg_state *n,
					struct spnego_data *spnego_in,
					NTSTATUS last_status,
					TALLOC_CTX *in_mem_ctx,
					DATA_BLOB *in_next)
{
	NTSTATUS status = NT_STATUS_INVALID_PARAMETER;
	bool allow_fallback = false;

	if (NT_STATUS_EQUAL(last_status, NT_STATUS_INVALID_PARAMETER) ||
	    NT_STATUS_EQUAL(last_status, NT_STATUS_CANT_ACCESS_DOMAIN_INFO))
	{
		allow_fallback = true;
	}

	if (!NT_STATUS_IS_OK(last_status)) {
		if (!allow_fallback) {
			DBG_WARNING("SPNEGO: Could not find a suitable mechtype "
				    " in NEG_TOKEN_INIT.\n");
			return NT_STATUS_INVALID_PARAMETER;
		}

		DBG_WARNING("SPNEGO(%s) NEG_TOKEN_INIT failed to parse contents: %s\n",
			    spnego_state->sub_sec_security->ops->name,
			    nt_errstr(last_status));

		/*
		 * Pretend we never started it
		 */
		gensec_spnego_update_sub_abort(spnego_state);

		/*
		 * And try the next one...
		 */
		n->mech_idx += 1;
	}

	/*
	 * we always reset all_idx here, as the negotiation is
	 * done via mech_idx!
	 */
	n->all_idx = 0;

	for (; n->mech_types[n->mech_idx] != NULL; n->mech_idx++) {
		const char *cur_mech = n->mech_types[n->mech_idx];
		const struct gensec_security_ops_wrapper *cur_sec = NULL;
		size_t i;

		gensec_spnego_update_sub_abort(spnego_state);

		for (i = 0; n->all_sec[i].op != NULL; i++) {
			if (strcmp(cur_mech, n->all_sec[i].oid) != 0) {
				continue;
			}

			cur_sec = &n->all_sec[i];
			n->all_idx = i;
			break;
		}

		if (cur_sec == NULL) {
			continue;
		}

		if (n->mech_idx != 0) {
			/*
			 * We can't use the optimistic token anymore.
			 */
			spnego_in->negTokenInit.mechToken = data_blob_null;
			/*
			 * Indicate the downgrade and request a
			 * mic.
			 */
			spnego_state->downgraded = true;
			spnego_state->mic_requested = true;
		}

		status = gensec_subcontext_start(spnego_state,
						 gensec_security,
						 &spnego_state->sub_sec_security);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}
		/* select the sub context */
		status = gensec_start_mech_by_ops(spnego_state->sub_sec_security,
						  cur_sec->op);
		if (!NT_STATUS_IS_OK(status)) {
			gensec_spnego_update_sub_abort(spnego_state);
			continue;
		}

		/* we need some content from the mech */
		*in_next = spnego_in->negTokenInit.mechToken;
		return NT_STATUS_MORE_PROCESSING_REQUIRED;
	}

	status = NT_STATUS_INVALID_PARAMETER;
	DBG_WARNING("SPNEGO: Could not find a suitable mechtype in "
		    "NEG_TOKEN_INIT, %s\n", nt_errstr(status));
	return status;
}

static NTSTATUS gensec_spnego_server_negTokenInit_finish(
					struct gensec_security *gensec_security,
					struct spnego_state *spnego_state,
					struct spnego_neg_state *n,
					struct spnego_data *spnego_in,
					NTSTATUS sub_status,
					const DATA_BLOB sub_out,
					TALLOC_CTX *out_mem_ctx,
					DATA_BLOB *out)
{
	const struct gensec_security_ops_wrapper *cur_sec =
			&n->all_sec[n->all_idx];
	DATA_BLOB mech_list_mic = data_blob_null;

	if (spnego_state->simulate_w2k) {
		/*
		 * Windows 2000 returns the unwrapped token
		 * also in the mech_list_mic field.
		 *
		 * In order to verify our client code,
		 * we need a way to have a server with this
		 * broken behaviour
		 */
		mech_list_mic = sub_out;
	}

	/*
	 * Note that 'cur_sec' is temporary memory, but
	 * cur_sec->oid points to a const string in the
	 * backends gensec_security_ops structure.
	 */
	spnego_state->neg_oid = cur_sec->oid;

	return gensec_spnego_server_response(spnego_state,
					     out_mem_ctx,
					     sub_status,
					     sub_out,
					     mech_list_mic,
					     out);
}

static const struct spnego_neg_ops gensec_spnego_server_negTokenInit_ops = {
	.name      = "server_negTokenInit",
	.start_fn  = gensec_spnego_server_negTokenInit_start,
	.step_fn   = gensec_spnego_server_negTokenInit_step,
	.finish_fn = gensec_spnego_server_negTokenInit_finish,
};

static NTSTATUS gensec_spnego_server_negTokenTarg_start(
					struct gensec_security *gensec_security,
					struct spnego_state *spnego_state,
					struct spnego_neg_state *n,
					struct spnego_data *spnego_in,
					TALLOC_CTX *in_mem_ctx,
					DATA_BLOB *in_next)
{
	NTSTATUS status;

	if (spnego_in->type != SPNEGO_NEG_TOKEN_TARG) {
		return NT_STATUS_INTERNAL_ERROR;
	}

	if (spnego_state->sub_sec_security == NULL) {
		DBG_ERR("SPNEGO: Did not setup a mech in NEG_TOKEN_INIT\n");
		return NT_STATUS_INTERNAL_ERROR;
	}

	spnego_state->num_targs++;

	status = gensec_spnego_update_pre_sub(gensec_security,
					      spnego_in);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	if (!spnego_state->sub_sec_ready) {
		*in_next = spnego_in->negTokenTarg.responseToken;
		return NT_STATUS_MORE_PROCESSING_REQUIRED;
	}

	return NT_STATUS_OK;
}

static NTSTATUS gensec_spnego_server_negTokenTarg_step(
					struct gensec_security *gensec_security,
					struct spnego_state *spnego_state,
					struct spnego_neg_state *n,
					struct spnego_data *spnego_in,
					NTSTATUS last_status,
					TALLOC_CTX *in_mem_ctx,
					DATA_BLOB *in_next)
{
	if (GENSEC_UPDATE_IS_NTERROR(last_status)) {
		return last_status;
	}

	return NT_STATUS_OK;
}

static NTSTATUS gensec_spnego_server_negTokenTarg_finish(
					struct gensec_security *gensec_security,
					struct spnego_state *spnego_state,
					struct spnego_neg_state *n,
					struct spnego_data *spnego_in,
					NTSTATUS sub_status,
					const DATA_BLOB sub_out,
					TALLOC_CTX *out_mem_ctx,
					DATA_BLOB *out)
{
	DATA_BLOB mech_list_mic = data_blob_null;

	if (spnego_state->sub_sec_ready) {
		NTSTATUS status;

		status = gensec_spnego_update_post_sub(gensec_security,
						       spnego_in, n,
						       &mech_list_mic);
		if (GENSEC_UPDATE_IS_NTERROR(status)) {
			return status;
		}
	}

	return gensec_spnego_server_response(spnego_state,
					     out_mem_ctx,
					     sub_status,
					     sub_out,
					     mech_list_mic,
					     out);
}

static const struct spnego_neg_ops gensec_spnego_server_negTokenTarg_ops = {
	.name      = "server_negTokenTarg",
	.start_fn  = gensec_spnego_server_negTokenTarg_start,
	.step_fn   = gensec_spnego_server_negTokenTarg_step,
	.finish_fn = gensec_spnego_server_negTokenTarg_finish,
};

static NTSTATUS gensec_spnego_create_negTokenInit_start(
					struct gensec_security *gensec_security,
					struct spnego_state *spnego_state,
					struct spnego_neg_state *n,
					struct spnego_data *spnego_in,
					TALLOC_CTX *in_mem_ctx,
					DATA_BLOB *in_next)
{
	n->mech_types = gensec_security_oids(gensec_security, n,
					     GENSEC_OID_SPNEGO);
	if (n->mech_types == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	n->all_idx = 0;
	n->all_sec = gensec_security_by_oid_list(gensec_security,
						 n, n->mech_types,
						 GENSEC_OID_SPNEGO);
	if (n->all_sec == NULL) {
		NTSTATUS status = NT_STATUS_INVALID_PARAMETER;
		DBG_DEBUG("Failed to setup SPNEGO negTokenInit request: "
			  "%s\n", nt_errstr(status));
		return status;
	}

	return n->ops->step_fn(gensec_security, spnego_state, n,
			       spnego_in, NT_STATUS_OK, in_mem_ctx, in_next);
}

static NTSTATUS gensec_spnego_create_negTokenInit_step(
					struct gensec_security *gensec_security,
					struct spnego_state *spnego_state,
					struct spnego_neg_state *n,
					struct spnego_data *spnego_in,
					NTSTATUS last_status,
					TALLOC_CTX *in_mem_ctx,
					DATA_BLOB *in_next)
{
	*in_next = data_blob_null;

	if (!NT_STATUS_IS_OK(last_status)) {
		const struct gensec_security_ops_wrapper *cur_sec =
			&n->all_sec[n->all_idx];
		const char *next = NULL;
		const char *principal = NULL;
		int dbg_level = DBGLVL_WARNING;

		if (cur_sec[1].op != NULL) {
			next = cur_sec[1].op->name;
			dbg_level = DBGLVL_NOTICE;
		}

		if (gensec_security->target.principal != NULL) {
			principal = gensec_security->target.principal;
		} else if (gensec_security->target.service != NULL &&
			   gensec_security->target.hostname != NULL)
		{
			principal = talloc_asprintf(spnego_state->sub_sec_security,
						    "%s/%s",
						    gensec_security->target.service,
						    gensec_security->target.hostname);
		} else {
			principal = gensec_security->target.hostname;
		}

		DBG_PREFIX(dbg_level,
			   ("SPNEGO(%s) creating NEG_TOKEN_INIT "
			    "for %s failed (next[%s]): %s\n",
			    spnego_state->sub_sec_security->ops->name,
			    principal, next, nt_errstr(last_status)));

		/*
		 * Pretend we never started it
		 */
		data_blob_free(&spnego_state->mech_types);
		gensec_spnego_update_sub_abort(spnego_state);

		/*
		 * And try the next one...
		 */
		n->all_idx += 1;
	}

	if (NT_STATUS_IS_OK(last_status)) {
		/*
		 * Use a resonable default in case we just started,
		 * This is the case where we're called from
		 * gensec_spnego_create_negTokenInit_start().
		 */
		last_status = NT_STATUS_INVALID_PARAMETER;
	}

	for (; n->all_sec[n->all_idx].op != NULL; n->all_idx++) {
		const struct gensec_security_ops_wrapper *cur_sec =
			&n->all_sec[n->all_idx];
		NTSTATUS status;
		bool ok;

		data_blob_free(&spnego_state->mech_types);
		gensec_spnego_update_sub_abort(spnego_state);

		status = gensec_subcontext_start(spnego_state,
						 gensec_security,
						 &spnego_state->sub_sec_security);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}
		/* select the sub context */
		status = gensec_start_mech_by_ops(spnego_state->sub_sec_security,
						  cur_sec->op);
		if (!NT_STATUS_IS_OK(status)) {
			gensec_spnego_update_sub_abort(spnego_state);
			continue;
		}

		n->mech_types = gensec_security_oids_from_ops_wrapped(n, cur_sec);
		if (n->mech_types == NULL) {
			DEBUG(1, ("SPNEGO: Failed to generate send_mech_types\n"));
			return NT_STATUS_NO_MEMORY;
		}

		ok = spnego_write_mech_types(spnego_state,
					     n->mech_types,
					     &spnego_state->mech_types);
		if (!ok) {
			DEBUG(1, ("SPNEGO: Failed to write mechTypes\n"));
			return NT_STATUS_NO_MEMORY;
		}

		/* In the client, try and produce the first (optimistic) packet */
		if (spnego_state->state_position == SPNEGO_CLIENT_START) {
			return NT_STATUS_MORE_PROCESSING_REQUIRED;
		}

		return NT_STATUS_OK;
	}

	DBG_DEBUG("Failed to setup SPNEGO negTokenInit request: "
		  "%s\n", nt_errstr(last_status));
	return last_status;
}

static NTSTATUS gensec_spnego_create_negTokenInit_finish(
					struct gensec_security *gensec_security,
					struct spnego_state *spnego_state,
					struct spnego_neg_state *n,
					struct spnego_data *spnego_in,
					NTSTATUS sub_status,
					const DATA_BLOB sub_out,
					TALLOC_CTX *out_mem_ctx,
					DATA_BLOB *out)
{
	const struct gensec_security_ops_wrapper *cur_sec =
			&n->all_sec[n->all_idx];
	struct spnego_data spnego_out;

	spnego_out.type = SPNEGO_NEG_TOKEN_INIT;

	/* List the remaining mechs as options */
	spnego_out.negTokenInit.mechTypes = n->mech_types;
	spnego_out.negTokenInit.reqFlags = data_blob_null;
	spnego_out.negTokenInit.reqFlagsPadding = 0;

	if (spnego_state->state_position == SPNEGO_SERVER_START) {
		spnego_out.negTokenInit.mechListMIC
			= data_blob_string_const(ADS_IGNORE_PRINCIPAL);
	} else {
		spnego_out.negTokenInit.mechListMIC = data_blob_null;
	}

	spnego_out.negTokenInit.mechToken = sub_out;

	if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
		DEBUG(1, ("Failed to write NEG_TOKEN_INIT\n"));
		return NT_STATUS_INVALID_PARAMETER;
	}

	/*
	 * Note that 'cur_sec' is temporary memory, but
	 * cur_sec->oid points to a const string in the
	 * backends gensec_security_ops structure.
	 */
	spnego_state->neg_oid = cur_sec->oid;

	/* set next state */
	if (spnego_state->state_position == SPNEGO_SERVER_START) {
		spnego_state->state_position = SPNEGO_SERVER_START;
		spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT;
	} else {
		spnego_state->state_position = SPNEGO_CLIENT_TARG;
		spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
	}

	return NT_STATUS_MORE_PROCESSING_REQUIRED;
}

static const struct spnego_neg_ops gensec_spnego_create_negTokenInit_ops = {
	.name      = "create_negTokenInit",
	.start_fn  = gensec_spnego_create_negTokenInit_start,
	.step_fn   = gensec_spnego_create_negTokenInit_step,
	.finish_fn = gensec_spnego_create_negTokenInit_finish,
};

/** create a server negTokenTarg 
 *
 * This is the case, where the client is the first one who sends data
*/

static NTSTATUS gensec_spnego_server_response(struct spnego_state *spnego_state,
					      TALLOC_CTX *out_mem_ctx,
					      NTSTATUS nt_status,
					      const DATA_BLOB unwrapped_out,
					      DATA_BLOB mech_list_mic,
					      DATA_BLOB *out)
{
	struct spnego_data spnego_out;

	/* compose reply */
	spnego_out.type = SPNEGO_NEG_TOKEN_TARG;
	spnego_out.negTokenTarg.responseToken = unwrapped_out;
	spnego_out.negTokenTarg.mechListMIC = mech_list_mic;
	spnego_out.negTokenTarg.supportedMech = NULL;

	if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {	
		spnego_out.negTokenTarg.supportedMech = spnego_state->neg_oid;
		if (spnego_state->mic_requested) {
			spnego_out.negTokenTarg.negResult = SPNEGO_REQUEST_MIC;
			spnego_state->mic_requested = false;
		} else {
			spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_INCOMPLETE;
		}
		spnego_state->state_position = SPNEGO_SERVER_TARG;
	} else if (NT_STATUS_IS_OK(nt_status)) {
		if (unwrapped_out.data) {
			spnego_out.negTokenTarg.supportedMech = spnego_state->neg_oid;
		}
		spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_COMPLETED;
		spnego_state->state_position = SPNEGO_DONE;
	} else {
		spnego_out.negTokenTarg.negResult = SPNEGO_REJECT;
		spnego_out.negTokenTarg.mechListMIC = data_blob_null;
		DEBUG(2, ("SPNEGO login failed: %s\n", nt_errstr(nt_status)));
		spnego_state->state_position = SPNEGO_DONE;
	}

	if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
		DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_TARG\n"));
		return NT_STATUS_INVALID_PARAMETER;
	}

	spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
	spnego_state->num_targs++;

	return nt_status;
}

static NTSTATUS gensec_spnego_update_pre_sub(struct gensec_security *gensec_security,
					     struct spnego_data *spnego)
{
	struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data;
	NTSTATUS nt_status;

	if (spnego->type != SPNEGO_NEG_TOKEN_TARG) {
		return NT_STATUS_OK;
	}

if (gensec_security->gensec_role == GENSEC_CLIENT) {
	if (spnego->negTokenTarg.negResult == SPNEGO_REQUEST_MIC) {
		spnego_state->mic_requested = true;
	}

	if (spnego->negTokenTarg.mechListMIC.length > 0) {
		DATA_BLOB *m = &spnego->negTokenTarg.mechListMIC;
		const DATA_BLOB *r = &spnego->negTokenTarg.responseToken;

		/*
		 * Windows 2000 has a bug, it repeats the
		 * responseToken in the mechListMIC field.
		 */
		if (m->length == r->length) {
			int cmp;

			cmp = memcmp(m->data, r->data, m->length);
			if (cmp == 0) {
				data_blob_free(m);
			}
		}
	}

	if (spnego->negTokenTarg.mechListMIC.length > 0) {
		if (spnego_state->sub_sec_ready) {
			spnego_state->needs_mic_check = true;
		}
	}
}
	if (spnego_state->needs_mic_check) {
		if (spnego->negTokenTarg.responseToken.length != 0) {
			DEBUG(1, ("SPNEGO: Did not setup a mech in NEG_TOKEN_INIT\n"));
			return NT_STATUS_INVALID_PARAMETER;
		}

if (gensec_security->gensec_role == GENSEC_CLIENT) {
		if (spnego->negTokenTarg.mechListMIC.length == 0
		    && spnego_state->may_skip_mic_check) {
			/*
			 * In this case we don't require
			 * a mechListMIC from the server.
			 *
			 * This works around bugs in the Azure
			 * and Apple spnego implementations.
			 *
			 * See
			 * https://bugzilla.samba.org/show_bug.cgi?id=11994
			 */
			spnego_state->needs_mic_check = false;
			return NT_STATUS_OK;
		}
}
		nt_status = gensec_check_packet(spnego_state->sub_sec_security,
						spnego_state->mech_types.data,
						spnego_state->mech_types.length,
						spnego_state->mech_types.data,
						spnego_state->mech_types.length,
						&spnego->negTokenTarg.mechListMIC);
		if (!NT_STATUS_IS_OK(nt_status)) {
			DEBUG(2,("GENSEC SPNEGO: failed to verify mechListMIC: %s\n",
				nt_errstr(nt_status)));
			return nt_status;
		}
		spnego_state->needs_mic_check = false;
		spnego_state->done_mic_check = true;
		return NT_STATUS_OK;
	}

	return NT_STATUS_OK;
}

static NTSTATUS gensec_spnego_update_post_sub(struct gensec_security *gensec_security,
					      struct spnego_data *spnego,
					      TALLOC_CTX *mem_ctx,
					      DATA_BLOB *mech_list_mic)
{
	struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data;
	NTSTATUS nt_status;

	*mech_list_mic = data_blob_null;

	if (!spnego_state->sub_sec_ready) {
		return NT_STATUS_INTERNAL_ERROR;
	}

	if (spnego->type != SPNEGO_NEG_TOKEN_TARG) {
		return NT_STATUS_OK;
	}

	if (!spnego_state->prepared_mic_check) {
		bool have_sign = true;
		bool new_spnego = false;

		have_sign = gensec_have_feature(spnego_state->sub_sec_security,
						GENSEC_FEATURE_SIGN);
		if (spnego_state->simulate_w2k) {
			have_sign = false;
		}

		new_spnego = gensec_have_feature(spnego_state->sub_sec_security,
						 GENSEC_FEATURE_NEW_SPNEGO);

if (gensec_security->gensec_role == GENSEC_CLIENT) {
		switch (spnego->negTokenTarg.negResult) {
		case SPNEGO_ACCEPT_COMPLETED:
		case SPNEGO_NONE_RESULT:
			if (spnego_state->num_targs == 1) {
				/*
				 * the first exchange doesn't require
				 * verification
				 */
				new_spnego = false;
			}

			break;

		case SPNEGO_ACCEPT_INCOMPLETE:
			if (spnego->negTokenTarg.mechListMIC.length > 0) {
				new_spnego = true;
				break;
			}

			if (spnego_state->downgraded) {
				/*
				 * A downgrade should be protected if
				 * supported
				 */
				break;
			}

			/*
			 * The caller may just asked for
			 * GENSEC_FEATURE_SESSION_KEY, this
			 * is only reflected in the want_features.
			 *
			 * As it will imply
			 * gensec_have_features(GENSEC_FEATURE_SIGN)
			 * to return true.
			 */
			if (gensec_security->want_features & GENSEC_FEATURE_SIGN) {
				break;
			}
			if (gensec_security->want_features & GENSEC_FEATURE_SEAL) {
				break;
			}
			/*
			 * Here we're sure our preferred mech was
			 * selected by the server and our caller doesn't
			 * need GENSEC_FEATURE_SIGN nor
			 * GENSEC_FEATURE_SEAL support.
			 *
			 * In this case we don't require
			 * a mechListMIC from the server.
			 *
			 * This works around bugs in the Azure
			 * and Apple spnego implementations.
			 *
			 * See
			 * https://bugzilla.samba.org/show_bug.cgi?id=11994
			 */
			spnego_state->may_skip_mic_check = true;
			break;

		case SPNEGO_REQUEST_MIC:
			if (spnego->negTokenTarg.mechListMIC.length > 0) {
				new_spnego = true;
			}
			break;
		default:
			break;
		}

		if (spnego_state->mic_requested) {
			if (have_sign) {
				new_spnego = true;
			}
		}
} else {
			if (spnego_state->num_targs == 0) {
				/*
				 * the first exchange doesn't require
				 * verification
				 */
				new_spnego = false;
			}
}

		if (spnego->negTokenTarg.mechListMIC.length > 0) {
			/*
			 * If we got the signature we should always
			 * check it.
			 */
if (gensec_security->gensec_role == GENSEC_CLIENT) {
			spnego_state->needs_mic_check = true;
} else {
			new_spnego = true;
}
		}

		if (have_sign && new_spnego) {
			spnego_state->needs_mic_check = true;
			spnego_state->needs_mic_sign = true;
		}

		spnego_state->prepared_mic_check = true;
	}

	if (spnego_state->needs_mic_check &&
	    spnego->negTokenTarg.mechListMIC.length > 0)
	{
		nt_status = gensec_check_packet(spnego_state->sub_sec_security,
						spnego_state->mech_types.data,
						spnego_state->mech_types.length,
						spnego_state->mech_types.data,
						spnego_state->mech_types.length,
						&spnego->negTokenTarg.mechListMIC);
		if (!NT_STATUS_IS_OK(nt_status)) {
			DEBUG(2,("GENSEC SPNEGO: failed to verify mechListMIC: %s\n",
				nt_errstr(nt_status)));
			return nt_status;
		}
		spnego_state->needs_mic_check = false;
		spnego_state->done_mic_check = true;
	}

	if (spnego_state->needs_mic_sign) {
		nt_status = gensec_sign_packet(spnego_state->sub_sec_security,
					       mem_ctx,
					       spnego_state->mech_types.data,
					       spnego_state->mech_types.length,
					       spnego_state->mech_types.data,
					       spnego_state->mech_types.length,
					       mech_list_mic);
		if (!NT_STATUS_IS_OK(nt_status)) {
			DEBUG(2,("GENSEC SPNEGO: failed to sign mechListMIC: %s\n",
				nt_errstr(nt_status)));
			return nt_status;
		}
		spnego_state->needs_mic_sign = false;
	}

	if (spnego_state->needs_mic_check) {
		return NT_STATUS_MORE_PROCESSING_REQUIRED;
	}

	return NT_STATUS_OK;
}

struct gensec_spnego_update_state {
	struct tevent_context *ev;
	struct gensec_security *gensec;
	struct spnego_state *spnego;

	DATA_BLOB full_in;
	struct spnego_data _spnego_in;
	struct spnego_data *spnego_in;

	struct {
		bool needed;
		DATA_BLOB in;
		NTSTATUS status;
		DATA_BLOB out;
	} sub;

	struct spnego_neg_state *n;

	NTSTATUS status;
	DATA_BLOB out;
};

static void gensec_spnego_update_cleanup(struct tevent_req *req,
					 enum tevent_req_state req_state)
{
	struct gensec_spnego_update_state *state =
		tevent_req_data(req,
		struct gensec_spnego_update_state);

	switch (req_state) {
	case TEVENT_REQ_USER_ERROR:
	case TEVENT_REQ_TIMED_OUT:
	case TEVENT_REQ_NO_MEMORY:
		/*
		 * A fatal error, further updates are not allowed.
		 */
		state->spnego->state_position = SPNEGO_DONE;
		break;
	default:
		break;
	}
}

static NTSTATUS gensec_spnego_update_in(struct gensec_security *gensec_security,
					const DATA_BLOB in, TALLOC_CTX *mem_ctx,
					DATA_BLOB *full_in);
static void gensec_spnego_update_pre(struct tevent_req *req);
static void gensec_spnego_update_done(struct tevent_req *subreq);
static void gensec_spnego_update_post(struct tevent_req *req);
static NTSTATUS gensec_spnego_update_out(struct gensec_security *gensec_security,
					 TALLOC_CTX *out_mem_ctx,
					 DATA_BLOB *_out);

static struct tevent_req *gensec_spnego_update_send(TALLOC_CTX *mem_ctx,
						    struct tevent_context *ev,
						    struct gensec_security *gensec_security,
						    const DATA_BLOB in)
{
	struct spnego_state *spnego_state =
		talloc_get_type_abort(gensec_security->private_data,
		struct spnego_state);
	struct tevent_req *req = NULL;
	struct gensec_spnego_update_state *state = NULL;
	struct tevent_req *subreq = NULL;
	NTSTATUS status;
	ssize_t len;

	req = tevent_req_create(mem_ctx, &state,
				struct gensec_spnego_update_state);
	if (req == NULL) {
		return NULL;
	}
	state->ev = ev;
	state->gensec = gensec_security;
	state->spnego = spnego_state;
	tevent_req_set_cleanup_fn(req, gensec_spnego_update_cleanup);

	if (spnego_state->out_frag.length > 0) {
		if (in.length > 0) {
			tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
			return tevent_req_post(req, ev);
		}

		status = gensec_spnego_update_out(gensec_security,
						  state, &state->out);
		if (GENSEC_UPDATE_IS_NTERROR(status)) {
			tevent_req_nterror(req, status);
			return tevent_req_post(req, ev);
		}

		state->status = status;
		tevent_req_done(req);
		return tevent_req_post(req, ev);
	}

	status = gensec_spnego_update_in(gensec_security, in,
					 state, &state->full_in);
	state->status = status;
	if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
		tevent_req_done(req);
		return tevent_req_post(req, ev);
	}
	if (tevent_req_nterror(req, status)) {
		return tevent_req_post(req, ev);
	}

	/* Check if we got a valid SPNEGO blob... */

	switch (spnego_state->state_position) {
	case SPNEGO_FALLBACK:
		break;

	case SPNEGO_CLIENT_TARG:
	case SPNEGO_SERVER_TARG:
		if (state->full_in.length == 0) {
			tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
			return tevent_req_post(req, ev);
		}

		/* fall through */
	case SPNEGO_CLIENT_START:
	case SPNEGO_SERVER_START:

		if (state->full_in.length == 0) {
			/* create_negTokenInit later */
			state->spnego_in = NULL;
			break;
		}

		len = spnego_read_data(state,
				       state->full_in,
				       &state->_spnego_in);
		if (len == -1) {
			if (spnego_state->state_position != SPNEGO_SERVER_START) {
				DEBUG(1, ("Invalid SPNEGO request:\n"));
				dump_data(1, state->full_in.data,
					  state->full_in.length);
				tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
				return tevent_req_post(req, ev);
			}

			status = gensec_spnego_server_try_fallback(gensec_security,
								   spnego_state,
								   state,
								   state->full_in);
			if (tevent_req_nterror(req, status)) {
				return tevent_req_post(req, ev);
			}

			/*
			 * We'll continue with SPNEGO_FALLBACK below...
			 */
			break;
		}
		state->spnego_in = &state->_spnego_in;

		/* OK, so it's real SPNEGO, check the packet's the one we expect */
		if (state->spnego_in->type != spnego_state->expected_packet) {
			DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n",
				  state->spnego_in->type,
				  spnego_state->expected_packet));
			dump_data(1, state->full_in.data,
				  state->full_in.length);
			tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
			return tevent_req_post(req, ev);
		}

		break;

	default:
		smb_panic(__location__);
		return NULL;
	}

	gensec_spnego_update_pre(req);
	if (!tevent_req_is_in_progress(req)) {
		return tevent_req_post(req, ev);
	}

	if (state->sub.needed) {
		/*
		 * We may need one more roundtrip...
		 */
		subreq = gensec_update_send(state, state->ev,
					    spnego_state->sub_sec_security,
					    state->sub.in);
		if (tevent_req_nomem(subreq, req)) {
			return tevent_req_post(req, ev);
		}
		tevent_req_set_callback(subreq,
					gensec_spnego_update_done,
					req);
		state->sub.needed = false;
		return req;
	}

	gensec_spnego_update_post(req);
	if (!tevent_req_is_in_progress(req)) {
		return tevent_req_post(req, ev);
	}

	return req;
}

static NTSTATUS gensec_spnego_update_in(struct gensec_security *gensec_security,
					const DATA_BLOB in, TALLOC_CTX *mem_ctx,
					DATA_BLOB *full_in)
{
	struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data;
	size_t expected;
	bool ok;

	*full_in = data_blob_null;

	switch (spnego_state->state_position) {
	case SPNEGO_FALLBACK:
		*full_in = in;
		spnego_state->in_needed = 0;
		return NT_STATUS_OK;

	case SPNEGO_CLIENT_START:
	case SPNEGO_CLIENT_TARG:
	case SPNEGO_SERVER_START:
	case SPNEGO_SERVER_TARG:
		break;

	case SPNEGO_DONE:
	default:
		return NT_STATUS_INVALID_PARAMETER;
	}

	if (spnego_state->in_needed == 0) {
		size_t size = 0;
		int ret;

		/*
		 * try to work out the size of the full
		 * input token, it might be fragmented
		 */
		ret = asn1_peek_full_tag(in,  ASN1_APPLICATION(0), &size);
		if ((ret != 0) && (ret != EAGAIN)) {
			ret = asn1_peek_full_tag(in, ASN1_CONTEXT(1), &size);
		}

		if ((ret == 0) || (ret == EAGAIN)) {
			spnego_state->in_needed = size;
		} else {
			/*
			 * If it is not an asn1 message
			 * just call the next layer.
			 */
			spnego_state->in_needed = in.length;
		}
	}

	if (spnego_state->in_needed > UINT16_MAX) {
		/*
		 * limit the incoming message to 0xFFFF
		 * to avoid DoS attacks.
		 */
		return NT_STATUS_INVALID_BUFFER_SIZE;
	}

	if ((spnego_state->in_needed > 0) && (in.length == 0)) {
		/*
		 * If we reach this, we know we got at least
		 * part of an asn1 message, getting 0 means
		 * the remote peer wants us to spin.
		 */
		return NT_STATUS_INVALID_PARAMETER;
	}

	expected = spnego_state->in_needed - spnego_state->in_frag.length;
	if (in.length > expected) {
		/*
		 * we got more than expected
		 */
		return NT_STATUS_INVALID_PARAMETER;
	}

	if (in.length == spnego_state->in_needed) {
		/*
		 * if the in.length contains the full blob
		 * we are done.
		 *
		 * Note: this implies spnego_state->in_frag.length == 0,
		 *       but we do not need to check this explicitly
		 *       because we already know that we did not get
		 *       more than expected.
		 */
		*full_in = in;
		spnego_state->in_needed = 0;
		return NT_STATUS_OK;
	}

	ok = data_blob_append(spnego_state, &spnego_state->in_frag,
			      in.data, in.length);
	if (!ok) {
		return NT_STATUS_NO_MEMORY;
	}

	if (spnego_state->in_needed > spnego_state->in_frag.length) {
		return NT_STATUS_MORE_PROCESSING_REQUIRED;
	}

	*full_in = spnego_state->in_frag;
	talloc_steal(mem_ctx, full_in->data);
	spnego_state->in_frag = data_blob_null;
	spnego_state->in_needed = 0;
	return NT_STATUS_OK;
}

static void gensec_spnego_update_pre(struct tevent_req *req)
{
	struct gensec_spnego_update_state *state =
		tevent_req_data(req,
		struct gensec_spnego_update_state);
	struct spnego_state *spnego_state = state->spnego;
	const const struct spnego_neg_ops *ops = NULL;
	NTSTATUS status;

	state->sub.needed = false;
	state->sub.in = data_blob_null;
	state->sub.status = NT_STATUS_INTERNAL_DB_CORRUPTION;
	state->sub.out = data_blob_null;

	if (spnego_state->state_position == SPNEGO_FALLBACK) {
		state->sub.in = state->full_in;
		state->full_in = data_blob_null;
		state->sub.needed = true;
		return;
	}

	if (state->spnego_in == NULL) {
		/*
		 * This is the initial creation of negTokenInit
		 * as a client or server after getting a empty
		 * input blob
		 */
		ops = &gensec_spnego_create_negTokenInit_ops;
		goto start_neg;
	}

	switch (spnego_state->state_position) {
	case SPNEGO_CLIENT_START:
		/*
		 * This is the initial creation of negTokenInit
		 * as a client, while we also got a negTokenInit
		 * from the server.
		 */
		ops = &gensec_spnego_client_negTokenInit_ops;
		break;

	case SPNEGO_CLIENT_TARG:
		ops = &gensec_spnego_client_negTokenTarg_ops;
		break;

	case SPNEGO_SERVER_START:
		ops = &gensec_spnego_server_negTokenInit_ops;
		break;

	case SPNEGO_SERVER_TARG:
		ops = &gensec_spnego_server_negTokenTarg_ops;
		break;

	default:
		smb_panic(__location__);
	}

start_neg:
	state->n = gensec_spnego_neg_state(state, ops);
	if (tevent_req_nomem(state->n, req)) {
		return;
	}

	status = ops->start_fn(state->gensec, spnego_state, state->n,
			       state->spnego_in, state, &state->sub.in);
	if (GENSEC_UPDATE_IS_NTERROR(status)) {
		tevent_req_nterror(req, status);
		return;
	}
	if (NT_STATUS_IS_OK(status)) {
		state->sub.status = NT_STATUS_OK;
		return;
	}

	state->sub.needed = true;
}

static void gensec_spnego_update_done(struct tevent_req *subreq)
{
	struct tevent_req *req =
		tevent_req_callback_data(subreq,
		struct tevent_req);
	struct gensec_spnego_update_state *state =
		tevent_req_data(req,
		struct gensec_spnego_update_state);
	struct spnego_state *spnego_state = state->spnego;

	state->sub.status = gensec_update_recv(subreq, state, &state->sub.out);
	TALLOC_FREE(subreq);
	if (NT_STATUS_IS_OK(state->sub.status)) {
		spnego_state->sub_sec_ready = true;
	}

	gensec_spnego_update_post(req);
}

static void gensec_spnego_update_post(struct tevent_req *req)
{
	struct gensec_spnego_update_state *state =
		tevent_req_data(req,
		struct gensec_spnego_update_state);
	struct spnego_state *spnego_state = state->spnego;
	struct tevent_req *subreq = NULL;
	NTSTATUS status;

	state->sub.in = data_blob_null;
	state->sub.needed = false;

	if (spnego_state->state_position == SPNEGO_FALLBACK) {
		spnego_state->out_status = state->sub.status;
		spnego_state->out_frag = state->sub.out;
		talloc_steal(spnego_state, spnego_state->out_frag.data);
		goto respond;
	}

	if (state->n != NULL) {
		const struct spnego_neg_ops *ops = state->n->ops;

		if (!GENSEC_UPDATE_IS_NTERROR(state->sub.status)) {
			status = ops->finish_fn(state->gensec,
						spnego_state,
						state->n,
						state->spnego_in,
						state->sub.status,
						state->sub.out,
						spnego_state,
						&spnego_state->out_frag);
			TALLOC_FREE(state->n);
			if (GENSEC_UPDATE_IS_NTERROR(status)) {
				tevent_req_nterror(req, status);
				return;
			}
			goto finished;
		}

		status = ops->step_fn(state->gensec,
				      spnego_state,
				      state->n,
				      state->spnego_in,
				      state->sub.status,
				      state,
				      &state->sub.in);
		if (GENSEC_UPDATE_IS_NTERROR(status)) {
			tevent_req_nterror(req, status);
			return;
		}
		if (NT_STATUS_IS_OK(status)) {
			status = ops->finish_fn(state->gensec,
						spnego_state,
						state->n,
						state->spnego_in,
						state->sub.status,
						state->sub.out,
						spnego_state,
						&spnego_state->out_frag);
			TALLOC_FREE(state->n);
			if (GENSEC_UPDATE_IS_NTERROR(status)) {
				tevent_req_nterror(req, status);
				return;
			}
			goto finished;
		}

		state->sub.status = NT_STATUS_INTERNAL_ERROR;
		state->sub.out = data_blob_null;

		/*
		 * We may need one more roundtrip...
		 */
		subreq = gensec_update_send(state, state->ev,
					    spnego_state->sub_sec_security,
					    state->sub.in);
		if (tevent_req_nomem(subreq, req)) {
			return;
		}
		tevent_req_set_callback(subreq,
					gensec_spnego_update_done,
					req);
		return;
	}

	// TODO
	status = NT_STATUS_INTERNAL_ERROR;

finished:
	if (NT_STATUS_IS_OK(status)) {
		bool reset_full = true;

		reset_full = !spnego_state->done_mic_check;

		status = gensec_may_reset_crypto(spnego_state->sub_sec_security,
						 reset_full);
		if (tevent_req_nterror(req, status)) {
			return;
		}
	}

	spnego_state->out_status = status;

respond:
	status = gensec_spnego_update_out(state->gensec,
					  state, &state->out);
	if (GENSEC_UPDATE_IS_NTERROR(status)) {
		tevent_req_nterror(req, status);
		return;
	}

	state->status = status;
	tevent_req_done(req);
	return;
}

static NTSTATUS gensec_spnego_update_out(struct gensec_security *gensec_security,
					 TALLOC_CTX *out_mem_ctx,
					 DATA_BLOB *_out)
{
	struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data;
	DATA_BLOB out = data_blob_null;
	bool ok;

	*_out = data_blob_null;

	if (spnego_state->out_frag.length <= spnego_state->out_max_length) {
		/*
		 * Fast path, we can deliver everything
		 */

		*_out = spnego_state->out_frag;
		if (spnego_state->out_frag.length > 0) {
			talloc_steal(out_mem_ctx, _out->data);
			spnego_state->out_frag = data_blob_null;
		}

		if (!NT_STATUS_IS_OK(spnego_state->out_status)) {
			return spnego_state->out_status;
		}

		/*
		 * We're completely done, further updates are not allowed.
		 */
		spnego_state->state_position = SPNEGO_DONE;
		return gensec_child_ready(gensec_security,
					  spnego_state->sub_sec_security);
	}

	out = spnego_state->out_frag;

	/*
	 * copy the remaining bytes
	 */
	spnego_state->out_frag = data_blob_talloc(spnego_state,
					out.data + spnego_state->out_max_length,
					out.length - spnego_state->out_max_length);
	if (spnego_state->out_frag.data == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	/*
	 * truncate the buffer
	 */
	ok = data_blob_realloc(spnego_state, &out,
			       spnego_state->out_max_length);
	if (!ok) {
		return NT_STATUS_NO_MEMORY;
	}

	talloc_steal(out_mem_ctx, out.data);
	*_out = out;
	return NT_STATUS_MORE_PROCESSING_REQUIRED;
}

static NTSTATUS gensec_spnego_update_recv(struct tevent_req *req,
					  TALLOC_CTX *out_mem_ctx,
					  DATA_BLOB *out)
{
	struct gensec_spnego_update_state *state =
		tevent_req_data(req,
		struct gensec_spnego_update_state);
	NTSTATUS status;

	*out = data_blob_null;

	if (tevent_req_is_nterror(req, &status)) {
		tevent_req_received(req);
		return status;
	}

	*out = state->out;
	talloc_steal(out_mem_ctx, state->out.data);
	status = state->status;
	tevent_req_received(req);
	return status;
}

static const char *gensec_spnego_oids[] = { 
	GENSEC_OID_SPNEGO,
	NULL 
};

static const struct gensec_security_ops gensec_spnego_security_ops = {
	.name		  = "spnego",
	.sasl_name	  = "GSS-SPNEGO",
	.auth_type	  = DCERPC_AUTH_TYPE_SPNEGO,
	.oid              = gensec_spnego_oids,
	.client_start     = gensec_spnego_client_start,
	.server_start     = gensec_spnego_server_start,
	.update_send	  = gensec_spnego_update_send,
	.update_recv	  = gensec_spnego_update_recv,
	.seal_packet	  = gensec_child_seal_packet,
	.sign_packet	  = gensec_child_sign_packet,
	.sig_size	  = gensec_child_sig_size,
	.max_wrapped_size = gensec_child_max_wrapped_size,
	.max_input_size	  = gensec_child_max_input_size,
	.check_packet	  = gensec_child_check_packet,
	.unseal_packet	  = gensec_child_unseal_packet,
	.wrap             = gensec_child_wrap,
	.unwrap           = gensec_child_unwrap,
	.session_key	  = gensec_child_session_key,
	.session_info     = gensec_child_session_info,
	.want_feature     = gensec_child_want_feature,
	.have_feature     = gensec_child_have_feature,
	.expire_time      = gensec_child_expire_time,
	.final_auth_type  = gensec_child_final_auth_type,
	.enabled          = true,
	.priority         = GENSEC_SPNEGO
};

_PUBLIC_ NTSTATUS gensec_spnego_init(TALLOC_CTX *ctx)
{
	NTSTATUS ret;
	ret = gensec_register(ctx, &gensec_spnego_security_ops);
	if (!NT_STATUS_IS_OK(ret)) {
		DEBUG(0,("Failed to register '%s' gensec backend!\n",
			gensec_spnego_security_ops.name));
		return ret;
	}

	return ret;
}
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: OpenPGP digital signature
URL: <http://lists.samba.org/pipermail/samba-technical/attachments/20170702/272beeda/signature.sig>


More information about the samba-technical mailing list