static SECURITY_STATUS SEC_ENTRY kerberos_InitializeSecurityContextA(
    PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq,
    ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
    PCtxtHandle phNewContext, PSecBufferDesc pOutput, ULONG* pfContextAttr, PTimeStamp ptsExpiry)
{
#ifdef WITH_GSSAPI
	KRB_CREDENTIALS* credentials;
	KRB_CONTEXT* context;
	KRB_CONTEXT new_context = { 0 };
	krb5_error_code rv;
	PSecBuffer input_buffer = NULL;
	PSecBuffer output_buffer = NULL;
	char* sname = NULL;
	char* host = NULL;
	krb5_data input_token = { 0 };
	krb5_data output_token = { 0 };
	SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
	WinPrAsn1_OID oid = { 0 };
	uint16_t tok_id = 0;
	krb5_ap_rep_enc_part* reply = NULL;
	krb5_error* error = NULL;
	krb5_flags ap_flags = AP_OPTS_USE_SUBKEY;
	char cksum_contents[24] = { 0 };
	krb5_data cksum = { 0, 24, cksum_contents };
	krb5_creds in_creds = { 0 };
	krb5_creds* creds = NULL;

	credentials = sspi_SecureHandleGetLowerPointer(phCredential);
	context = sspi_SecureHandleGetLowerPointer(phContext);

	if (!credentials)
		return SEC_E_NO_CREDENTIALS;

	if (pInput)
		input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN);
	if (pOutput)
		output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN);

	if (fContextReq & ISC_REQ_MUTUAL_AUTH)
		ap_flags |= AP_OPTS_MUTUAL_REQUIRED;

	if (fContextReq & ISC_REQ_USE_SESSION_KEY)
		ap_flags |= AP_OPTS_USE_SESSION_KEY;

	if (!context)
	{
		context = &new_context;

		if ((rv = krb5_init_context(&context->ctx)))
			return SEC_E_INTERNAL_ERROR;

		if (fContextReq & ISC_REQ_USE_SESSION_KEY)
		{
			context->state = KERBEROS_STATE_TGT_REQ;
			context->u2u = TRUE;
		}
		else
			context->state = KERBEROS_STATE_AP_REQ;
	}
	else
	{
		if (!input_buffer || !sspi_gss_unwrap_token(input_buffer, &oid, &tok_id, &input_token))
			goto bad_token;
		if ((context->u2u && !sspi_gss_oid_compare(&oid, &kerberos_u2u_OID)) ||
		    (!context->u2u && !sspi_gss_oid_compare(&oid, &kerberos_OID)))
			goto bad_token;
	}

	/* Split target name into service/hostname components */
	if (pszTargetName)
	{
		sname = _strdup(pszTargetName);
		if (!sname)
		{
			status = SEC_E_INSUFFICIENT_MEMORY;
			goto cleanup;
		}
		host = strchr(sname, '/');
		if (host)
			*host++ = 0;
	}

	/* SSPI flags are compatible with GSS flags except INTEG_FLAG */
	context->flags |= (fContextReq & 0x1F);
	if (fContextReq & ISC_REQ_INTEGRITY && !(fContextReq & ISC_REQ_NO_INTEGRITY))
		context->flags |= SSPI_GSS_C_INTEG_FLAG;

	switch (context->state)
	{
		case KERBEROS_STATE_TGT_REQ:

			if (!kerberos_mk_tgt_token(output_buffer, KRB_TGT_REQ, sname, host, NULL))
				goto cleanup;

			context->state = KERBEROS_STATE_TGT_REP;

			break;

		case KERBEROS_STATE_TGT_REP:

			if (tok_id != TOK_ID_TGT_REP)
				goto bad_token;

			if (!kerberos_rd_tgt_token(&input_token, NULL, &in_creds.second_ticket))
				goto bad_token;

			/* Continue to AP-REQ */

		case KERBEROS_STATE_AP_REQ:

			/* Set auth_context options */
			if ((rv = krb5_auth_con_init(context->ctx, &context->auth_ctx)))
				goto cleanup;
			if ((rv = krb5_auth_con_setflags(context->ctx, context->auth_ctx,
			                                 KRB5_AUTH_CONTEXT_DO_SEQUENCE |
			                                     KRB5_AUTH_CONTEXT_USE_SUBKEY)))
				goto cleanup;
			if ((rv = krb5_auth_con_set_req_cksumtype(context->ctx, context->auth_ctx,
			                                          GSS_CHECKSUM_TYPE)))
				goto cleanup;

			/* Write the checksum (channel binding and delegation not implemented) */
			Data_Write_UINT32(cksum_contents, 16);
			Data_Write_UINT32((cksum_contents + 20), context->flags);

			/* Get a service ticket */
			if ((rv = krb5_sname_to_principal(context->ctx, host, sname, KRB5_NT_SRV_HST,
			                                  &in_creds.server)))
				goto cleanup;

			if ((rv = krb5_cc_get_principal(context->ctx, credentials->ccache, &in_creds.client)))
				goto cleanup;

			if ((rv = krb5_get_credentials(context->ctx, context->u2u ? KRB5_GC_USER_USER : 0,
			                               credentials->ccache, &in_creds, &creds)))
				goto cleanup;

			/* Make the AP_REQ message */
			if ((rv = krb5_mk_req_extended(context->ctx, &context->auth_ctx, ap_flags, &cksum,
			                               creds, &output_token)))
				goto cleanup;

			if (!sspi_gss_wrap_token(output_buffer,
			                         context->u2u ? &kerberos_u2u_OID : &kerberos_OID,
			                         TOK_ID_AP_REQ, &output_token))
				goto cleanup;

			if (context->flags & SSPI_GSS_C_SEQUENCE_FLAG)
			{
				krb5_auth_con_getlocalseqnumber(context->ctx, context->auth_ctx,
				                                (INT32*)&context->local_seq);
				context->remote_seq ^= context->local_seq;
			}

			krb5_auth_con_getkey_k(context->ctx, context->auth_ctx, &context->session_key);
			krb5_auth_con_getsendsubkey_k(context->ctx, context->auth_ctx, &context->initiator_key);

			context->state = KERBEROS_STATE_AP_REP;

			break;

		case KERBEROS_STATE_AP_REP:

			if (tok_id == TOK_ID_AP_REP)
			{
				if ((rv = krb5_rd_rep(context->ctx, context->auth_ctx, &input_token, &reply)))
					goto cleanup;
				krb5_free_ap_rep_enc_part(context->ctx, reply);
			}
			else if (tok_id == TOK_ID_ERROR)
			{
				if (!(rv = krb5_rd_error(context->ctx, &input_token, &error)))
				{
					WLog_ERR(TAG, "KRB_ERROR: %s", error->text.data);
					krb5_free_error(context->ctx, error);
				}
				goto cleanup;
			}
			else
				goto bad_token;

			if (context->flags & SSPI_GSS_C_SEQUENCE_FLAG)
				krb5_auth_con_getremoteseqnumber(context->ctx, context->auth_ctx,
				                                 (INT32*)&context->remote_seq);

			krb5_auth_con_getrecvsubkey_k(context->ctx, context->auth_ctx, &context->acceptor_key);

			context->state = KERBEROS_STATE_FINAL;

			break;

		case KERBEROS_STATE_FINAL:
		default:
			WLog_ERR(TAG, "Kerberos in invalid state!");
			goto cleanup;
	}

	/* On first call allocate a new context */
	if (new_context.ctx)
	{
		context = kerberos_ContextNew();
		if (!context)
		{
			status = SEC_E_INSUFFICIENT_MEMORY;
			goto cleanup;
		}
		CopyMemory(context, &new_context, sizeof(KRB_CONTEXT));
	}

	if (context->state == KERBEROS_STATE_FINAL)
	{
		output_buffer->cbBuffer = 0;
		status = SEC_E_OK;
	}
	else
		status = SEC_I_CONTINUE_NEEDED;

	sspi_SecureHandleSetLowerPointer(phNewContext, context);
	sspi_SecureHandleSetUpperPointer(phNewContext, KERBEROS_SSP_NAME);

cleanup:

	if (rv)
		kerberos_log_msg(context->ctx, rv);

	if (output_token.data)
		krb5_free_data_contents(context->ctx, &output_token);
	if (sname)
		free(sname);

	if (context == &new_context)
	{
		if (context->auth_ctx)
			krb5_auth_con_free(context->ctx, context->auth_ctx);
		if (context->ctx)
			krb5_free_context(context->ctx);
	}

	return status;

bad_token:
	status = SEC_E_INVALID_TOKEN;
	goto cleanup;
#else
	return SEC_E_UNSUPPORTED_FUNCTION;
#endif /* WITH_GSSAPI */
}
static SECURITY_STATUS SEC_ENTRY kerberos_InitializeSecurityContextA(
    PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq,
    ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
    PCtxtHandle phNewContext, PSecBufferDesc pOutput, ULONG* pfContextAttr, PTimeStamp ptsExpiry)
{
#ifdef WITH_GSSAPI
	KRB_CREDENTIALS* credentials;
	KRB_CONTEXT* context;
	KRB_CONTEXT new_context = { 0 };
	krb5_error_code rv;
	PSecBuffer input_buffer = NULL;
	PSecBuffer output_buffer = NULL;
	char* sname = NULL;
	char* host = NULL;
	krb5_data input_token = { 0 };
	krb5_data output_token = { 0 };
	SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
	WinPrAsn1_OID oid = { 0 };
	uint16_t tok_id = 0;
	krb5_ap_rep_enc_part* reply = NULL;
	krb5_error* error = NULL;
	krb5_flags ap_flags = AP_OPTS_USE_SUBKEY;
	char cksum_contents[24] = { 0 };
	krb5_data cksum = { 0, 24, cksum_contents };
	krb5_creds in_creds = { 0 };
	krb5_creds* creds = NULL;

	credentials = sspi_SecureHandleGetLowerPointer(phCredential);
	context = sspi_SecureHandleGetLowerPointer(phContext);

	if (!credentials)
		return SEC_E_NO_CREDENTIALS;

	if (pInput)
		input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN);
	if (pOutput)
		output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN);

	if (fContextReq & ISC_REQ_MUTUAL_AUTH)
		ap_flags |= AP_OPTS_MUTUAL_REQUIRED;

	if (fContextReq & ISC_REQ_USE_SESSION_KEY)
		ap_flags |= AP_OPTS_USE_SESSION_KEY;

	if (!context)
	{
		context = &new_context;

		if ((rv = krb5_init_context(&context->ctx)))
			return SEC_E_INTERNAL_ERROR;

		if (fContextReq & ISC_REQ_USE_SESSION_KEY)
		{
			context->state = KERBEROS_STATE_TGT_REQ;
			context->u2u = TRUE;
		}
		else
			context->state = KERBEROS_STATE_AP_REQ;
	}
	else
	{
		if (!input_buffer || !sspi_gss_unwrap_token(input_buffer, &oid, &tok_id, &input_token))
			goto bad_token;
		if ((context->u2u && !sspi_gss_oid_compare(&oid, &kerberos_u2u_OID)) ||
		    (!context->u2u && !sspi_gss_oid_compare(&oid, &kerberos_OID)))
			goto bad_token;
	}

	 
	if (pszTargetName)
	{
		sname = _strdup(pszTargetName);
		if (!sname)
		{
			status = SEC_E_INSUFFICIENT_MEMORY;
			goto cleanup;
		}
		host = strchr(sname, '/');
		if (host)
			*host++ = 0;
	}

	 
	context->flags |= (fContextReq & 0x1F);
	if (fContextReq & ISC_REQ_INTEGRITY && !(fContextReq & ISC_REQ_NO_INTEGRITY))
		context->flags |= SSPI_GSS_C_INTEG_FLAG;

	switch (context->state)
	{
		case KERBEROS_STATE_TGT_REQ:

			if (!kerberos_mk_tgt_token(output_buffer, KRB_TGT_REQ, sname, host, NULL))
				goto cleanup;

			context->state = KERBEROS_STATE_TGT_REP;

			break;

		case KERBEROS_STATE_TGT_REP:

			if (tok_id != TOK_ID_TGT_REP)
				goto bad_token;

			if (!kerberos_rd_tgt_token(&input_token, NULL, &in_creds.second_ticket))
				goto bad_token;

			 

		case KERBEROS_STATE_AP_REQ:

			 
			if ((rv = krb5_auth_con_init(context->ctx, &context->auth_ctx)))
				goto cleanup;
			if ((rv = krb5_auth_con_setflags(context->ctx, context->auth_ctx,
			                                 KRB5_AUTH_CONTEXT_DO_SEQUENCE |
			                                     KRB5_AUTH_CONTEXT_USE_SUBKEY)))
				goto cleanup;
			if ((rv = krb5_auth_con_set_req_cksumtype(context->ctx, context->auth_ctx,
			                                          GSS_CHECKSUM_TYPE)))
				goto cleanup;

			 
			Data_Write_UINT32(cksum_contents, 16);
			Data_Write_UINT32((cksum_contents + 20), context->flags);

			 
			if ((rv = krb5_sname_to_principal(context->ctx, host, sname, KRB5_NT_SRV_HST,
			                                  &in_creds.server)))
				goto cleanup;

			if ((rv = krb5_cc_get_principal(context->ctx, credentials->ccache, &in_creds.client)))
				goto cleanup;

			if ((rv = krb5_get_credentials(context->ctx, context->u2u ? KRB5_GC_USER_USER : 0,
			                               credentials->ccache, &in_creds, &creds)))
				goto cleanup;

			 
			if ((rv = krb5_mk_req_extended(context->ctx, &context->auth_ctx, ap_flags, &cksum,
			                               creds, &output_token)))
				goto cleanup;

			if (!sspi_gss_wrap_token(output_buffer,
			                         context->u2u ? &kerberos_u2u_OID : &kerberos_OID,
			                         TOK_ID_AP_REQ, &output_token))
				goto cleanup;

			if (context->flags & SSPI_GSS_C_SEQUENCE_FLAG)
			{
				krb5_auth_con_getlocalseqnumber(context->ctx, context->auth_ctx,
				                                (INT32*)&context->local_seq);
				context->remote_seq ^= context->local_seq;
			}

			krb5_auth_con_getkey_k(context->ctx, context->auth_ctx, &context->session_key);
			krb5_auth_con_getsendsubkey_k(context->ctx, context->auth_ctx, &context->initiator_key);

			context->state = KERBEROS_STATE_AP_REP;

			break;

		case KERBEROS_STATE_AP_REP:

			if (tok_id == TOK_ID_AP_REP)
			{
				if ((rv = krb5_rd_rep(context->ctx, context->auth_ctx, &input_token, &reply)))
					goto cleanup;
				krb5_free_ap_rep_enc_part(context->ctx, reply);
			}
			else if (tok_id == TOK_ID_ERROR)
			{
				if (!(rv = krb5_rd_error(context->ctx, &input_token, &error)))
				{
					WLog_ERR(TAG, "KRB_ERROR: %s", error->text.data);
					krb5_free_error(context->ctx, error);
				}
				goto cleanup;
			}
			else
				goto bad_token;

			if (context->flags & SSPI_GSS_C_SEQUENCE_FLAG)
				krb5_auth_con_getremoteseqnumber(context->ctx, context->auth_ctx,
				                                 (INT32*)&context->remote_seq);

			krb5_auth_con_getrecvsubkey_k(context->ctx, context->auth_ctx, &context->acceptor_key);

			context->state = KERBEROS_STATE_FINAL;

			break;

		case KERBEROS_STATE_FINAL:
		default:
			WLog_ERR(TAG, "Kerberos in invalid state!");
			goto cleanup;
	}

	 
	if (new_context.ctx)
	{
		context = kerberos_ContextNew();
		if (!context)
		{
			status = SEC_E_INSUFFICIENT_MEMORY;
			goto cleanup;
		}
		CopyMemory(context, &new_context, sizeof(KRB_CONTEXT));
	}

	if (context->state == KERBEROS_STATE_FINAL)
	{
		output_buffer->cbBuffer = 0;
		status = SEC_E_OK;
	}
	else
		status = SEC_I_CONTINUE_NEEDED;

	sspi_SecureHandleSetLowerPointer(phNewContext, context);
	sspi_SecureHandleSetUpperPointer(phNewContext, KERBEROS_SSP_NAME);

cleanup:

	if (rv)
		kerberos_log_msg(context->ctx, rv);

	if (output_token.data)
		krb5_free_data_contents(context->ctx, &output_token);
	if (sname)
		free(sname);

	if (context == &new_context)
	{
		if (context->auth_ctx)
			krb5_auth_con_free(context->ctx, context->auth_ctx);
		if (context->ctx)
			krb5_free_context(context->ctx);
	}

	return status;

bad_token:
	status = SEC_E_INVALID_TOKEN;
	goto cleanup;
#else
	return SEC_E_UNSUPPORTED_FUNCTION;
#endif  
}