Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

755 lines
34 KiB

/****************************************************************************/
/* asmcpp.cpp */
/* */
/* Security Manager C++ functions */
/* */
/* Copyright (C) 1997-1999 Microsoft Corporation */
/****************************************************************************/
#include <precomp.h>
#pragma hdrstop
#define TRC_FILE "asmcpp"
#define pTRCWd (pRealSMHandle->pWDHandle)
#include <adcg.h>
#include <as_conf.hpp>
extern "C"
{
#include <asmint.h>
#include <slicense.h>
}
#define DC_INCLUDE_DATA
#include <asmdata.c>
#undef DC_INCLUDE_DATA
/****************************************************************************/
/* Name: SM_Register */
/* */
/* Purpose: Register with SM */
/* */
/* Returns: TRUE - registered OK */
/* FALSE - register failed */
/* */
/* Params: pSMHandle - SM handle */
/* pMaxPDUSize - max PDU size supported (returned) */
/* pUserID - this person's user ID (returned) */
/* */
/* Operation: This function enables the Share Class to register with SM. */
/* This allows */
/* - the Share Class to call SM */
/* - SM to issue callbacks to the Share Class (SC_SMCallback). */
/****************************************************************************/
BOOL RDPCALL SM_Register(
PVOID pSMHandle,
PUINT32 pMaxPDUSize,
PUINT32 pUserID)
{
PSM_HANDLE_DATA pRealSMHandle = (PSM_HANDLE_DATA)pSMHandle;
BOOL rc = FALSE;
DC_BEGIN_FN("SM_Register");
// Console stacks do not go through the typical key negotiation, so update
// the state appropriately
if (pRealSMHandle->pWDHandle->StackClass == Stack_Console)
{
TRC_ALT((TB, "Console security state to SM_STATE_SM_CONNECTED"));
SM_SET_STATE(SM_STATE_CONNECTED);
}
//Skip the following CHECK_STATE. For the console reconnect,
// this is a legal transition.
//SM_CHECK_STATE(SM_EVT_REGISTER);
/************************************************************************/
/* Calculate max PDU size allowed to caller */
/************************************************************************/
*pMaxPDUSize = pRealSMHandle->maxPDUSize -
pRealSMHandle->encryptHeaderLen;
TRC_NRM((TB, "Max PDU size allowed to core is %d", *pMaxPDUSize));
/************************************************************************/
/* Return the user ID */
/************************************************************************/
*pUserID = pRealSMHandle->userID;
TRC_NRM((TB, "Returning user id %d", *pUserID));
SM_SET_STATE(SM_STATE_SC_REGISTERED);
pRealSMHandle->bForwardDataToSC = TRUE;
rc = TRUE;
//DC_EXIT_POINT:
DC_END_FN();
return rc;
} /* SM_Register */
/****************************************************************************/
/* Name: SM_OnConnected */
/* */
/* Purpose: Handle connection state change callbacks from NM */
/* */
/* Returns: none */
/* */
/* Params: pRealSMHandle - SM Handle */
/* userID - userID of the node causing the callback */
/* result - result of connection attempt */
/* pUserData - Network (Server-Client) user data */
/* maxPDUSize - max size of PDUs that can be sent */
/****************************************************************************/
void RDPCALL SM_OnConnected(
PVOID pSMHandle,
UINT32 userID,
UINT32 result,
PRNS_UD_SC_NET pUserData,
UINT32 maxPDUSize)
{
PSM_HANDLE_DATA pRealSMHandle = (PSM_HANDLE_DATA)pSMHandle;
DC_BEGIN_FN("SM_OnConnected");
if (result == NM_CB_CONN_OK)
{
TRC_NRM((TB, "Connected OK as user %x", userID));
SM_CHECK_STATE(SM_EVT_CONNECTED);
/********************************************************************/
/* Store useful stuff in SM Handle */
/********************************************************************/
pRealSMHandle->userID = userID;
pRealSMHandle->maxPDUSize = maxPDUSize;
pRealSMHandle->channelID = pUserData->MCSChannelID;
/********************************************************************/
// Pass the result to WDW. For WDW this is the start of the
// connection sequence.
/********************************************************************/
SM_SET_STATE(SM_STATE_SM_CONNECTING);
WDW_OnSMConnecting(pRealSMHandle->pWDHandle, pRealSMHandle->pUserData,
pUserData);
/********************************************************************/
// Free the reply user data once we've passed it to WDW.
/********************************************************************/
if (pRealSMHandle->pUserData != NULL)
{
TRC_NRM((TB, "Free user data"));
COM_Free(pRealSMHandle->pUserData);
pRealSMHandle->pUserData = NULL;
}
}
else
{
TRC_NRM((TB, "Failed to connect, reason %d", result));
/********************************************************************/
// Tell WDW
/********************************************************************/
WDW_OnSMConnected(pRealSMHandle->pWDHandle, result);
/********************************************************************/
/* Clean up */
/********************************************************************/
SM_SET_STATE(SM_STATE_SM_CONNECTING);
SM_Disconnect(pRealSMHandle);
}
DC_EXIT_POINT:
DC_END_FN();
} /* SM_OnConnected */
/****************************************************************************/
/* Name: SM_OnDisconnected */
/* */
/* Purpose: Handle disconnection state change callback from NM */
/* */
/* Params: pRealSMHandle - SM Handle */
/* userID - userID of the node causing the callback */
/* result - reason for the disconnection */
/****************************************************************************/
void RDPCALL SM_OnDisconnected(
PVOID pSMHandle,
UINT32 userID,
UINT32 result)
{
ShareClass *pSC;
PSM_HANDLE_DATA pRealSMHandle = (PSM_HANDLE_DATA)pSMHandle;
DC_BEGIN_FN("SM_OnDisconnected");
SM_CHECK_STATE(SM_EVT_DISCONNECTED);
TRC_NRM((TB, "Disconnected, reason %d", result));
/************************************************************************/
/* First, clear up connection resources */
/************************************************************************/
SMFreeConnectResources(pRealSMHandle);
SM_SET_STATE(SM_STATE_INITIALIZED);
/************************************************************************/
// Tell SC. Don't call if SC is not registered.
/************************************************************************/
if (pRealSMHandle->state == SM_STATE_SC_REGISTERED) {
// Check that the Share Class exists.
pSC = (ShareClass *)(pRealSMHandle->pWDHandle->dcShare);
if (pSC != NULL) {
// Call SC's callback.
pSC->SC_OnDisconnected((UINT16)userID);
}
else {
TRC_ERR((TB, "No Share Class"));
}
}
else {
TRC_ERR((TB, "SC Not registered"));
}
/************************************************************************/
/* Then tell WDW */
/************************************************************************/
WDW_OnSMDisconnected(pRealSMHandle->pWDHandle);
DC_EXIT_POINT:
DC_END_FN();
} /* SM_OnDisconnected */
/****************************************************************************/
// SM_DecodeFastPathInput
//
// Handles decryption of fast-path input data if it's an encrypted packet.
// Then passes directly to IM for bytestream decoding and injection.
/****************************************************************************/
void RDPCALL SM_DecodeFastPathInput(
void *pSM,
BYTE *pData,
unsigned DataLength,
BOOL bEncrypted,
unsigned NumEvents,
BOOL fSafeChecksum)
{
BOOL rc;
PSM_HANDLE_DATA pRealSMHandle = (PSM_HANDLE_DATA)pSM;
ShareClass *pShareClass;
// Used if encypted using FIPS
BYTE *pEncData, *pSigData;
DWORD EncDataLen, dwPadLen;
DC_BEGIN_FN("SM_FastPathInputDecode");
// If we are being attacked or have a bad client, we may receive data
// here before we are really in a conference. If so, ignore it.
// We can't disconnect here because the code to do so requires pWDHandle
// to be valid. If the protocol stream is messed up, the connection will
// be dropped later by other decoding code.
if (pRealSMHandle->pWDHandle != NULL) {
pShareClass = (ShareClass *)pRealSMHandle->pWDHandle->dcShare;
if (pRealSMHandle->encrypting) {
if (bEncrypted) {
//
// Debug verification, we always go with what the protocol header
// says but verify it's consistent with the capabilities
//
if (pRealSMHandle->useSafeChecksumMethod != (fSafeChecksum != 0)) {
TRC_ERR((TB,
"fastpath: fSecureChecksum: 0x%x setting"
"does not match protocol: 0x%x",
pRealSMHandle->useSafeChecksumMethod,
fSafeChecksum));
}
// Make sure we have at least the size of the security context.
if (DataLength >= DATA_SIGNATURE_SIZE) {
// Check to see if we need to update the session key.
if (pRealSMHandle->decryptCount == UPDATE_SESSION_KEY_COUNT) {
rc = TRUE;
// Don't need to update the session key if using FIPS
if (pRealSMHandle->encryptionMethodSelected != SM_FIPS_ENCRYPTION_FLAG) {
rc = UpdateSessionKey(
pRealSMHandle->startDecryptKey,
pRealSMHandle->currentDecryptKey,
pRealSMHandle->encryptionMethodSelected,
pRealSMHandle->keyLength,
&pRealSMHandle->rc4DecryptKey,
pRealSMHandle->encryptionLevel);
}
if (rc) {
// Reset counter.
pRealSMHandle->decryptCount = 0;
}
else {
TRC_ERR((TB,"SM failed to update session key"));
goto FailedKey;
}
}
if (pRealSMHandle->encryptionMethodSelected == SM_FIPS_ENCRYPTION_FLAG) {
if (DataLength < (sizeof(RNS_SECURITY_HEADER2) - sizeof(RNS_SECURITY_HEADER))) {
TRC_ERR((TB,"PDU len %u too short for security context in FIPS decryption",
DataLength));
goto ShortData;
}
pEncData = pData + sizeof(RNS_SECURITY_HEADER2) - sizeof(RNS_SECURITY_HEADER);
pSigData = pEncData - MAX_SIGN_SIZE;
EncDataLen = DataLength - (sizeof(RNS_SECURITY_HEADER2) - sizeof(RNS_SECURITY_HEADER));
dwPadLen = *((TSUINT8 *)(pSigData - sizeof(TSUINT8)));
rc = TSFIPS_DecryptData(
&(pRealSMHandle->FIPSData),
pEncData,
EncDataLen,
dwPadLen,
pSigData,
pRealSMHandle->totalDecryptCount);
}
else {
// Encryption signature sits in first DATA_SIGNATURE_SIZE
// bytes of the provided packet data.
rc = DecryptData(
pRealSMHandle->encryptionLevel,
pRealSMHandle->currentDecryptKey,
&pRealSMHandle->rc4DecryptKey,
pRealSMHandle->keyLength,
pData + DATA_SIGNATURE_SIZE,
DataLength - DATA_SIGNATURE_SIZE,
pRealSMHandle->macSaltKey,
pData,
fSafeChecksum,
pRealSMHandle->totalDecryptCount);
}
if (rc) {
TRC_DBG((TB, "Data decrypted: %u",
DataLength - DATA_SIGNATURE_SIZE));
// Increment decryption counter.
pRealSMHandle->decryptCount++;
pRealSMHandle->totalDecryptCount++;
// Skip past the encryption signature for passing to IM.
if (pRealSMHandle->encryptionMethodSelected == SM_FIPS_ENCRYPTION_FLAG) {
pData = pEncData;
DataLength = EncDataLen - *((TSUINT8 *)(pSigData - sizeof(TSUINT8)));
}
else {
pData += DATA_SIGNATURE_SIZE;
DataLength -= DATA_SIGNATURE_SIZE;
}
}
else {
TRC_ERR((TB, "SM failed to decrypt data: len=%u",
DataLength - DATA_SIGNATURE_SIZE));
goto FailedDecrypt;
}
}
else {
TRC_ERR((TB,"PDU len %u too short for security context",
DataLength));
goto ShortData;
}
}
else {
// Need to disconnect if client only sends encrypted data
if (pRealSMHandle->pWDHandle->bForceEncryptedCSPDU) {
TRC_ASSERT((FALSE), (TB, "unencrypted data in encrypted protocol"));
goto FailedDecrypt;
}
}
}
// Be sure to decrypt before checking the dead and other state to
// maintain the correct context between the client and server.
if (!pRealSMHandle->dead && SM_CHECK_STATE_Q(SM_EVT_DATA_PACKET)) {
// We directly inject into the mouse and keyboard streams if we
// are a primary stack. We cannot receive fast-path data on a
// passthru stack since it does not get RawInput calls. Fast-path
// input cannot be received by a shadow stack since the passthru-
// to-shadow stack data format is always the non-fast-path
// format, munged from the fast-path format by
// IM_ConvertFastPathToShadow().
TRC_ASSERT((pRealSMHandle->pWDHandle->StackClass == Stack_Primary),
(TB,"Somehow we received fast-path input on a %s stack!",
(pRealSMHandle->pWDHandle->StackClass == Stack_Passthru ?
"passthru" :
pRealSMHandle->pWDHandle->StackClass == Stack_Shadow ?
"shadow" : "console")));
pShareClass->IM_DecodeFastPathInput(pData, DataLength, NumEvents);
if (pRealSMHandle->pWDHandle->shadowState == SHADOW_CLIENT)
pShareClass->IM_ConvertFastPathToShadow(pData, DataLength, NumEvents);
}
else {
TRC_ALT((TB,"Ignoring fast-path input PDU on dead or bad state"));
}
}
else {
TRC_ERR((TB,"Received fast-path input data before SM initialized, "
"ignoring"));
goto DataTooSoon;
}
DC_END_FN();
return;
// Error handling, segregate to keep out of performance path
// instruction cache.
FailedKey:
WDW_LogAndDisconnect(pRealSMHandle->pWDHandle, TRUE,
Log_RDP_ENC_UpdateSessionKeyFailed, NULL, 0);
return;
FailedDecrypt:
WDW_LogAndDisconnect(pRealSMHandle->pWDHandle, TRUE, Log_RDP_ENC_DecryptFailed,
NULL, 0);
return;
ShortData:
WDW_LogAndDisconnect(pRealSMHandle->pWDHandle, TRUE,
Log_RDP_SecurityDataTooShort, pData, DataLength);
return;
DataTooSoon:
// TODO: Combine the SM, NM, and TSWd state into one single struct
// containing everything we need, then fix this code to disconnect
// by using the pContext we need.
;
}
/****************************************************************************/
/* Name: SM_MCSSendDataCallback */
/* */
/* Purpose: Handle SendData callback from MCS */
/* */
/* Returns: TRUE if successful, FALSE otherwise. */
/* */
/* Params: hUser - MCS user handle for our user attachment */
/* UserDefined - our NM handle */
/* bUniform - Data received is from an MCS uniform-send-data */
/* hChannel - Handle of the receive channel */
/* Priority - MCS priority for the data */
/* SenderID - MCS UserID of the sender */
/* Segmentation - MCS segmentation flags for the data */
/* DataLength - Length of the incoming data */
/* pData - Pointer to (DataLength) sized memory block */
/****************************************************************************/
BOOLEAN __fastcall SM_MCSSendDataCallback(BYTE *pData,
unsigned DataLength,
void *UserDefined,
UserHandle hUser,
BOOLEAN bUniform,
ChannelHandle hChannel,
MCSPriority Priority,
UserID SenderID,
Segmentation Segmentation)
{
BOOLEAN result = TRUE;
PSM_HANDLE_DATA pRealSMHandle;
BOOL dataPkt;
BOOL licPkt;
UINT16 channelID;
ShareClass *dcShare;
DC_BEGIN_FN("SM_MCSSendDataCallback");
/************************************************************************/
/* SMHandle is assumed to be the first member in the NM struct pointed */
/* to by UserDefined. */
/************************************************************************/
pRealSMHandle = *((PSM_HANDLE_DATA *)UserDefined);
dcShare = (ShareClass*)pRealSMHandle->pWDHandle->dcShare;
/************************************************************************/
/* Check MCS segmentation. */
/************************************************************************/
TRC_ASSERT((Segmentation == (SEGMENTATION_BEGIN | SEGMENTATION_END)),
(TB,"Segmented packet received"));
/************************************************************************/
/* Decide what type of packet it is. This is a bit hokey. */
/* - If we are encrypting, the security header always tells us the type */
/* of packet. */
/* - If we are not encrypting */
/* - assume packets received in state SM_STATE_SC_REGISTERED are data */
/* packets */
/* - assume packets received in other states are not data packets. */
/************************************************************************/
if (pRealSMHandle->encrypting)
{
if (DataLength >= sizeof(RNS_SECURITY_HEADER)) {
dataPkt = !(((PRNS_SECURITY_HEADER_UA)pData)->flags &
RNS_SEC_NONDATA_PKT);
}
else {
TRC_ERR((TB,"Received pkt len %u too short for security header",
DataLength));
WDW_LogAndDisconnect(pRealSMHandle->pWDHandle, TRUE,
Log_RDP_SecurityDataTooShort, pData, DataLength);
result = FALSE;
DC_QUIT;
}
}
else
{
dataPkt = (pRealSMHandle->state == SM_STATE_SC_REGISTERED);
}
TRC_DBG((TB, "Encrypting=%d: %s packet",
pRealSMHandle->encrypting, dataPkt ? "data" : "security"));
/************************************************************************/
/* Handle data packets (perf path). */
/************************************************************************/
if (dataPkt)
{
/********************************************************************/
/* Decrypt the packet if necessary */
/********************************************************************/
if (pRealSMHandle->encrypting)
{
if (((PRNS_SECURITY_HEADER_UA)pData)->flags & RNS_SEC_ENCRYPT)
{
TRC_DBG((TB, "Decrypt the packet"));
if (SMDecryptPacket(pRealSMHandle, pData, DataLength,
pRealSMHandle->useSafeChecksumMethod))
{
TRC_NRM((TB,"Decrypted packet at %p", pData));
}
else
{
TRC_ERR((TB, "Failed to decrypt packet: %ld", DataLength));
DC_QUIT;
}
if (pRealSMHandle->encryptionMethodSelected == SM_FIPS_ENCRYPTION_FLAG) {
DataLength -= (sizeof(RNS_SECURITY_HEADER2) + ((PRNS_SECURITY_HEADER2_UA)pData)->padlen);
pData += sizeof(RNS_SECURITY_HEADER2);
}
else {
pData += sizeof(RNS_SECURITY_HEADER1);
DataLength -= sizeof(RNS_SECURITY_HEADER1);
}
}
else
{
/************************************************************/
/* Adjust pointer and length */
/************************************************************/
if (pRealSMHandle->pWDHandle->bForceEncryptedCSPDU) {
TRC_ASSERT((FALSE), (TB, "unencrypted data in encrypted protocol"));
WDW_LogAndDisconnect(
pRealSMHandle->pWDHandle, TRUE,
Log_RDP_ENC_DecryptFailed, NULL, 0);
result = FALSE;
DC_QUIT;
}
else {
TRC_NRM((TB, "Pass packet to SC"));
pData += sizeof(RNS_SECURITY_HEADER);
DataLength -= sizeof(RNS_SECURITY_HEADER);
}
}
}
/********************************************************************/
/* Don't do anything if we're dead */
/********************************************************************/
if (!pRealSMHandle->dead)
{
if (SM_CHECK_STATE_Q(SM_EVT_DATA_PACKET)) {
// Decide where to send the packet based on the channel ID.
channelID = (UINT16)MCSGetChannelIDFromHandle(hChannel);
if (channelID == pRealSMHandle->channelID) {
// Pass packet to SC. Don't do it if the ShareClass
// doesn't exist.
TRC_NRM((TB, "Share channel %x", channelID));
if (pRealSMHandle->pWDHandle->dcShare != NULL) {
// Only a non-shadowing primary stack, or a shadow
// stack should process the full set of PDUs
if (((pRealSMHandle->pWDHandle->StackClass == Stack_Primary) &&
(pRealSMHandle->pWDHandle->shadowState != SHADOW_CLIENT))) {
((ShareClass*)(pRealSMHandle->pWDHandle->dcShare))->
SC_OnDataReceived(pData, SenderID, DataLength,
Priority);
}
else if ((pRealSMHandle->pWDHandle->StackClass == Stack_Shadow)) {
UINT16 pduType = ((PTS_SHARECONTROLHEADER)pData)->pduType
& TS_MASK_PDUTYPE;
// Unless it's CLIENTRANDOM PDU, we can only forward
// data to Share Class if Share Class is ready.
// We could be in a racing condition that Share class
// hasn't finished initialization, but we have received
// shadow data.
if (pRealSMHandle->bForwardDataToSC == TRUE ||
pduType == TS_PDUTYPE_CLIENTRANDOMPDU) {
((ShareClass*)(pRealSMHandle->pWDHandle->dcShare))->
SC_OnDataReceived(pData, SenderID, DataLength,
Priority);
}
}
// Else send to SC for shadow hotkey processing or
// passthru from the shadow target to the shadow
// client.
else {
((ShareClass*)(pRealSMHandle->pWDHandle->dcShare))->
SC_OnShadowDataReceived(pData, SenderID, DataLength,
Priority);
}
}
else {
TRC_ERR((TB, "Tried to call non-existent Share Class"));
}
}
else
{
/************************************************************/
/* Virtual Channel */
/************************************************************/
TRC_NRM((TB, "Virtual channel %x", channelID));
WDW_OnDataReceived(pRealSMHandle->pWDHandle,
pData,
DataLength,
channelID);
}
}
else {
TRC_ALT((TB,"Ignoring PDU because of bad state"));
#ifdef INSTRUM_TRACK_DISCARDED
pRealSMHandle->nDiscardPDUBadState++;
#endif
DC_QUIT;
}
}
else
{
TRC_ERR((TB, "Recvd PDU when we're dead"));
//
// To help track down the VC decompression bug
// track if we dropped any VC packets
//
channelID = (UINT16)MCSGetChannelIDFromHandle(hChannel);
if (channelID != pRealSMHandle->channelID) {
//
// If this is VC data then we must hand it off
// to be decompressed otherwise the server's context
// will get out of sync with the client's
//
TRC_NRM((TB, "Virtual channel %x", channelID));
WDW_OnDataReceived(pRealSMHandle->pWDHandle,
pData,
DataLength,
channelID);
#ifdef INSTRUM_TRACK_DISCARDED
pRealSMHandle->nDiscardVCDataWhenDead++;
#endif
}
else
{
#ifdef INSTRUM_TRACK_DISCARDED
pRealSMHandle->nDiscardNonVCPDUWhenDead++;
#endif
}
DC_QUIT;
}
}
else
{
/********************************************************************/
/* If we're encrypting, the security header tells us the packet */
/* type. If we're not encrypting, we need to use our state to */
/* decide whether this is a licensing or security packet. */
/********************************************************************/
if (pRealSMHandle->encrypting)
{
licPkt = (((PRNS_SECURITY_HEADER_UA)pData)->flags &
RNS_SEC_LICENSE_PKT);
}
else
{
licPkt = (pRealSMHandle->state == SM_STATE_LICENSING);
}
if (licPkt)
{
#ifdef USE_LICENSE
/****************************************************************/
/* License packet */
/****************************************************************/
TRC_NRM((TB, "Licensing packet"));
SM_CHECK_STATE(SM_EVT_LIC_PACKET);
if (((PRNS_SECURITY_HEADER_UA)pData)->flags & RNS_SEC_ENCRYPT)
{
TRC_DBG((TB, "Decrypt the licensing packet"));
if (SMDecryptPacket(pRealSMHandle, pData, DataLength,
pRealSMHandle->useSafeChecksumMethod))
{
TRC_NRM((TB,"Decrypted packet at %p", pData));
}
else
{
TRC_ERR((TB, "Failed to decrypt packet: %ld", DataLength));
DC_QUIT;
}
if (pRealSMHandle->encryptionMethodSelected == SM_FIPS_ENCRYPTION_FLAG) {
DataLength -= (sizeof(RNS_SECURITY_HEADER2) + ((PRNS_SECURITY_HEADER2_UA)pData)->padlen);
pData += sizeof(RNS_SECURITY_HEADER2);
}
else {
pData += sizeof(RNS_SECURITY_HEADER1);
DataLength -= sizeof(RNS_SECURITY_HEADER1);
}
}
else
{
TRC_NRM((TB, "Licensing packet not encrypted"));
pData += sizeof(RNS_SECURITY_HEADER);
DataLength -= sizeof(RNS_SECURITY_HEADER);
}
SLicenseData(pRealSMHandle->pLicenseHandle,
pRealSMHandle,
pData,
DataLength);
#else /* USE_LICENSE */
TRC_ABORT((TB,"Licensing not implemented yet"));
#endif /* USE_LICENSE */
}
else
{
/****************************************************************/
/* Security packet */
/****************************************************************/
TRC_NRM((TB, "Security packet"));
SM_CHECK_STATE(SM_EVT_SEC_PACKET);
result = SMContinueSecurityExchange(pRealSMHandle, pData, DataLength);
}
}
DC_EXIT_POINT:
DC_END_FN();
return (result);
} /* SM_MCSSendDataCallback */