//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// This source code is licensed under Microsoft Shared Source License
// Version 1.0 for Windows CE.
// For a copy of the license visit http://go.microsoft.com/fwlink/?LinkId=3223.
//

// PPP Compression Control Protocol (CCP) Option Handling


//  Include Files

#include "windows.h"
#include "cclib.h"
#include "memory.h"
#include "cxport.h"

// VJ Compression Include Files

#include "ndis.h"
#include "tcpip.h"
#include "vjcomp.h"

// PPP Include Files

#include "protocol.h"
#include "ppp.h"
#include "lcp.h"
#include "ipcp.h"
#include "ccp.h"
#include "ncp.h"
#include "mac.h"
#include "ip_intf.h"
#include "ras.h"
#include "raserror.h"

#include "cclib.h"


//
//	Debug pretty print routine for CCP option data
//
void
ccpMSPPCOptionToStringCb(
	IN		PVOID  context,
	IN	OUT	POptionInfo pInfo,
	IN		PBYTE  pOptData,
	IN		DWORD  cbOptData,
	IN  OUT PSTR   *ppBuffer)
{
	PSTR   pBuffer = *ppBuffer;
	DWORD  bits;

	ASSERT(cbOptData == 4);

	*pBuffer = '\0';

	BYTES_TO_DWORD(bits, pOptData);

	if (bits & MCCP_COMPRESSION)
		strcat(pBuffer, "Compression,");
	if (bits & MSTYPE_ENCRYPTION_40F)
		strcat(pBuffer, "40-bit-encryption,");
	if (bits & MSTYPE_ENCRYPTION_56)
		strcat(pBuffer, "56-bit-encryption,");
	if (bits & MSTYPE_ENCRYPTION_128)
		strcat(pBuffer, "128-bit-encryption,");
	if (bits & MSTYPE_HISTORYLESS)
		strcat(pBuffer, "Stateless,");
	if (bits & MSTYPE_ENCRYPTION_40)
		strcat(pBuffer, "D(obsolete),");

	pBuffer += strlen(pBuffer);

	bits &= ~(MCCP_COMPRESSION|MSTYPE_ENCRYPTION_40F|MSTYPE_ENCRYPTION_56|MSTYPE_ENCRYPTION_128|MSTYPE_ENCRYPTION_40|MSTYPE_HISTORYLESS);
	if (bits)
		pBuffer += sprintf(pBuffer, "Unknown(%x)", bits);
	*ppBuffer = pBuffer;
}

void
ccpResetPeerOptionValuesCb(
	IN		PVOID  context)
//
//	This function is called whenever a new configure-request is
//	received from the peer prior to processing the options therein
//	contained. This function must reset all options to their default
//	values, such that any not explicity contained within the configure
//	request will use the default setting.
//
{
	PCCPContext pContext = (PCCPContext)context;

	pContext->peer.SupportedBits = 0;
}

void
ccpOptionValueReset(
	PCCPContext pContext)
//
//	Initialize the option states at the beginning of an
//	CCP negotiation.
//
{
	pppSession_t    *pSession = pContext->session;
	RASPENTRY		*pRasEntry = &pSession->rasEntry;

	DEBUGMSG( ZONE_NCP, ( TEXT( "PPP: ccpOptionValueReset\n" )));

	pContext->local.SupportedBits = 0;
	pContext->peer.SupportedBits = 0;

	if (pRasEntry->dwfOptions & RASEO_SwCompression)
		pContext->local.SupportedBits |= MCCP_COMPRESSION;

	if (pContext->bmEncryptionTypesSupported)
	{
		//
		//	If data encryption is required, then add encryption bits to those we put in our config-request.
		//  NOTE: RASEO_RequireDataEncryption is ignored unless RASEO_RequireMsEncryptedPw is also set.
		//
		if (NEED_ENCRYPTION(pRasEntry))
		{
		    pContext->local.SupportedBits |= pContext->bmEncryptionTypesSupported;	
		}
	}
}


/////////////	MSPPC option negotiation callbacks //////////////

DWORD
ccpBuildMSPPCOptionCb(
	IN		PVOID  context,
	IN	OUT	POptionInfo pInfo,
	OUT		PBYTE  pOptData,
	OUT		PDWORD pcbOptData)
//
//	Called to fill in the SupportedBits field for the MPPC option
//	that is part of a configure-request we will send to the peer.
//
{
	PCCPContext  pContext = (PCCPContext)context;
	DWORD	     dwResult = NO_ERROR;

	// Cannot do compression without a scratch Rx buffer
	if (pContext->ScratchRxBuf == NULL)
		pContext->local.SupportedBits &= ~MCCP_COMPRESSION;

	DWORD_TO_BYTES(pOptData, pContext->local.SupportedBits);
	*pcbOptData = 4;

	return dwResult;
}

DWORD
ccpAckMSPPCOptionCb(
	IN		PVOID  context,
	IN	OUT	POptionInfo pInfo,
	IN		PBYTE  pOptData,
	IN		DWORD  cbOptData)
//
//	Called when the peer ACKs the compression option
//	that we sent in a CR.
//
{
	PCCPContext  pContext = (PCCPContext)context;
	DWORD	     dwResult = NO_ERROR;
	DWORD		 bits;

	//
	// The generic option handling code should have rejected any illegal sized option.
	//
	ASSERT(cbOptData == 4);

	BYTES_TO_DWORD(bits, pOptData);

	if ((bits & (MSTYPE_ENCRYPTION_40F | MSTYPE_ENCRYPTION_128)) ==
				(MSTYPE_ENCRYPTION_40F | MSTYPE_ENCRYPTION_128))
	{
		//
		// The ACK should contain only one encryption type
		//
		DEBUGMSG(ZONE_WARN, (TEXT("PPP: CCP-WARNING: Peer ACKed with BOTH 128 and 40 bit encryption\n")));
	
		// Let's just try 128 bit encryption
		pContext->local.SupportedBits = bits & ~MSTYPE_ENCRYPTION_40F;

		//
		// This will make the FSM treat the ACK was a NAK to generate a new CR.
		//
		dwResult = ERROR_UNRECOGNIZED_RESPONSE;
	}
	else
	{
		//
		// Nothing to do here. When CCP enters the opened state we will act on
		// the negotiated bit settings in pContext->local.SupportedBits.
		//
		ASSERT(bits == pContext->local.SupportedBits);
	}

	return dwResult;
}

DWORD
ccpNakMSPPCOptionCb(
	IN		PVOID  context,
	IN OUT	struct OptionInfo *pInfo,
	IN		PBYTE  pOptData,
	IN		DWORD  cbOptData)
//
//	Called when the peer NAKs a compression option
//	that we sent in a CR.
//
{
	PCCPContext  pContext = (PCCPContext)context;
	pppSession_t *pSession = pContext->session;
	RASPENTRY	 *pRasEntry = &pSession->rasEntry;
	DWORD	     dwResult = NO_ERROR;
    DWORD		 dwPeerWantedBits,
		         dwCompressionWantedBits,
				 dwEncryptionWantedBits;

	//
	// The generic option handling code should have rejected any illegal sized option.
	//
	ASSERT(cbOptData == 4);

	BYTES_TO_DWORD(dwPeerWantedBits, pOptData);
	dwCompressionWantedBits = dwPeerWantedBits & MCCP_COMPRESSION;
	dwEncryptionWantedBits  = dwPeerWantedBits & (MSTYPE_ENCRYPTION_40F | MSTYPE_ENCRYPTION_128);

	//
	// If the peer wants compression then allow it unless compression is disabled
	// in our rasEntry.
	//
	if (dwCompressionWantedBits
	&&  (!(pRasEntry->dwfOptions & RASEO_SwCompression)))
	{
		DEBUGMSG(ZONE_NCP, (TEXT("CCP: Peer wants data compression but RASEO_SwCompression is off\n")));
		dwCompressionWantedBits = 0;
	}

	//
	// If we require data encryption, then we can't agree with a peer's
	// suggestion to not do encryption.
	//
	if (NEED_ENCRYPTION(pRasEntry)
	&&  ((dwEncryptionWantedBits & pContext->bmEncryptionTypesSupported) == 0))
	{
		// Modify the peer requested bits to pretend that an encryption type was requested.
		// This will give the peer another chance to accept encryption.

		DEBUGMSG(ZONE_WARN, (TEXT("PPP: CCP-WARNING: Peer NAKed with no encrypt but we need encrypt\n")));
		dwEncryptionWantedBits = pContext->bmEncryptionTypesSupported;
	}
	else if ((dwEncryptionWantedBits & (MSTYPE_ENCRYPTION_40F | MSTYPE_ENCRYPTION_128)) ==
									   (MSTYPE_ENCRYPTION_40F | MSTYPE_ENCRYPTION_128))
	{
		//
		// The NAK should contain only one encryption type
		//
		DEBUGMSG(ZONE_WARN, (TEXT("PPP: CCP-WARNING: Peer NAKed with both 128 and 40 bit encryption\n")));

		// Let's just try 128 bit encryption
		dwEncryptionWantedBits &= ~MSTYPE_ENCRYPTION_40F;
	}

	pContext->local.SupportedBits = dwCompressionWantedBits | dwEncryptionWantedBits;

	return dwResult;
}

DWORD
ccpRequestMSPPCOptionCb(
	IN	OUT	PVOID context,
	IN	OUT	POptionInfo pInfo,
		OUT	PBYTE		pCode,
	IN	OUT	PBYTE		*ppOptData,
	IN	OUT	PDWORD		pcbOptData)
//
//	Called when we receive a CCP configure request containing MPPC option,
//  or, when a CCP configure request from the peer fails to contain an option
//  that we need or want, in which case *ppOptData will be NULL.
//
{
	PCCPContext  pContext = (PCCPContext)context;
	pppSession_t *pSession = pContext->session;
	RASPENTRY    *pRasEntry = &pSession->rasEntry;
	DWORD	     dwResult = NO_ERROR;
	PBYTE		 pOptData = *ppOptData;
	DWORD		 cbOptData= *pcbOptData;
	DWORD        bmRequestBits,
		         bmResponseBits;

	bmRequestBits = 0;
	bmResponseBits = 0;
	if (pOptData == NULL)
	{
		//
		//	The peer did not send a compression option in its config-request,
		//  and we want to tell the peer the types that we want or require.
		//
		if (pRasEntry->dwfOptions & RASEO_SwCompression)
			bmResponseBits |= MCCP_COMPRESSION;

		if (pContext->bmEncryptionTypesSupported
		&&  NEED_ENCRYPTION(pRasEntry))
		{
		    bmResponseBits |= pContext->bmEncryptionTypesSupported;	
		}

		ASSERT(bmResponseBits != 0);
	}
	else
	{
		//
		// The generic option handling code should have rejected any illegal sized option.
		//
		ASSERT(cbOptData == 4);
		BYTES_TO_DWORD(bmRequestBits, pOptData);

		//
		//	If SW compression is allowed, then accept it if requested.
		//
		if(bmRequestBits & MCCP_COMPRESSION)
		{
			if (pRasEntry->dwfOptions & RASEO_SwCompression)
			{
				DEBUGMSG( ZONE_NCP | ZONE_FUNCTION, (TEXT("CCP: accept COMPRESSION REQUESTED BY PEER\n")));

				bmResponseBits |= MCCP_COMPRESSION;
			}
			else
			{
				DEBUGMSG( ZONE_NCP | ZONE_FUNCTION, (TEXT("CCP: deny COMPRESSION REQUESTED BY PEER\n")));

				// Just leave bmResponseBits unchanged
			}
		}

		// From IETF Draft on MPPE, section 2.1 Option Negotiation:
		//    "... the negotiation initiator SHOULD request all of the options
		//	   it supports.  The responder should NAK with a single encryption
		//	   option."
		//
		// Possible cases:
		//		1. Peer has set multiple encryption types (S and L)
		//			Response: NAK with strongest matching type that we support (S, L, OR none)
		//		2. Peer has set a single encryption type (S, L)
		//			Response: ACK requested type if supported, otherwise NAK with none
		//		3. Peer has requested no encryption
		//			Response: ACK no encryption
		//

		bmResponseBits |= (bmRequestBits & pContext->bmEncryptionTypesSupported);

		//
		//	If the peer offered both types of encryption, and we support both
		//	types, modify the response to only suggest a single (strongest) type.
		//
		if ((bmResponseBits & (MSTYPE_ENCRYPTION_40F | MSTYPE_ENCRYPTION_128)) == 
			(MSTYPE_ENCRYPTION_40F | MSTYPE_ENCRYPTION_128))
		{
			// Keep the 128 bit encryption, turn off the 40 bit.
			bmResponseBits &= ~MSTYPE_ENCRYPTION_40F;
		}

		//
		//	If RASEO_RequireDataEncryption has been set in the local RAS
		//	options, and our current response is for no encryption, then
		//	respond instead with the encryption types we support.
		//
		if (NEED_ENCRYPTION(pRasEntry)
		&&   ((bmResponseBits & (MSTYPE_ENCRYPTION_40F | MSTYPE_ENCRYPTION_128)) == 0) )
		{
			DEBUGMSG( ZONE_NCP, (TEXT( "CCP:RX MUST INSIST UPON ENCRYPTION!\n")));
			ASSERT(pContext->bmEncryptionTypesSupported != 0);
			bmResponseBits |= pContext->bmEncryptionTypesSupported;
		}

		//
		//	No support for stateless mode
		//
		if (bmRequestBits & MSTYPE_HISTORYLESS)
		{
			// No action required, just don't add it to response bits
		}
	}

	if( bmRequestBits == bmResponseBits )
    {
        // Packet OK - ACK it
		pContext->peer.SupportedBits = bmResponseBits;
		*pCode = PPP_CONFIGURE_ACK;
	}
    else
    {
        // Packet NOT OK - NAK it with corrections

        DEBUGMSG( ZONE_NCP, (TEXT( "CCP: NAK bits=%x, suggest=%x\n"), bmRequestBits, bmResponseBits) );

		DWORD_TO_BYTES(&pContext->abResponseBits[0], bmResponseBits);

		*pCode = PPP_CONFIGURE_NAK;
		*ppOptData = &pContext->abResponseBits[0];
		*pcbOptData = 4;
    }

	return dwResult;
}

OptionDescriptor ccpCompressionOptionDescriptor =
{
	CCP_OPT_MSC_CCP,	            // type == MS Compression/Encryption
	4,								// fixedLength = 4
	"MSC",                          // name
	ccpBuildMSPPCOptionCb,
	ccpAckMSPPCOptionCb,
	ccpNakMSPPCOptionCb,
	NULL,							// no special reject handling
	ccpRequestMSPPCOptionCb,
	ccpMSPPCOptionToStringCb		
};

DWORD
ccpOptionInit(
	PCCPContext pContext)
//
//	This function is called once during session initialization
//	to build the list of options that are supported.
//
{
	pppSession_t       *pSession = pContext->session;
	RASPENTRY		   *pRasEntry = &pSession->rasEntry;
	DWORD              dwResult;
	OptionRequireLevel orlMPPCELocal,
		               orlMPPCEPeer;

	if (pContext->bmEncryptionTypesSupported
	&&  NEED_ENCRYPTION(pRasEntry))
	{
		// Encryption is required
		orlMPPCELocal = ORL_Required;
		orlMPPCEPeer  = ORL_Required;
	}
	else if (pRasEntry->dwfOptions & RASEO_SwCompression)
	{
		// Compression is wanted
		orlMPPCELocal = ORL_Wanted;
		orlMPPCEPeer  = ORL_Wanted;
	}
	else if (pContext->bmEncryptionTypesSupported)
	{
		// Allow encryption if the peer requests it

		orlMPPCEPeer  = ORL_Allowed;

		//
		// Some servers appear to have a 
		// send them an empty CCP configure-request option list.
		// So, we always send our MPPCE option even when we don't ourselves
		// want any encryption/compression. This allows the server to see
		// an MSCCP option with a value of 0x00000000 which they understand.
		//
		orlMPPCELocal = ORL_Wanted;
	}
	else
	{
		// Encryption isn't supported, Compression is not allowed
		orlMPPCELocal = ORL_Unsupported;
		orlMPPCEPeer  = ORL_Unsupported;
	}

	dwResult = PppFsmOptionsAdd(pContext->pFsm,
							&ccpCompressionOptionDescriptor,    orlMPPCELocal, orlMPPCEPeer,
							NULL);

	return dwResult;
}
