windows-nt/Source/XPSP1/NT/ds/security/services/smartcrd/controls/statmon.cpp
2020-09-26 16:20:57 +08:00

671 lines
15 KiB
C++

/*++
Copyright (C) Microsoft Corporation, 1996 - 1999
Module Name:
StatMon
Abstract:
This file contains the implementation of CScStatusMonitor
(an object that watches for status changes of readers recognized
by the smart card service, handling PNP and requests for copies of
the status array)
Author:
Amanda Matlosz 02/26/98
Environment:
Win32, C++ with exceptions
Revision History:
Notes:
--*/
#include "statmon.h"
// forward declarations of private functions
UINT ScStatusChangeProc(LPVOID pParam);
/////
// CScStatusMonitor
CScStatusMonitor::~CScStatusMonitor()
{
if (stopped != m_status)
{
Stop();
}
}
/*++
Start:
The monitor will fail to start if:
Any of the arguments are missing.
Two SCARDCONTEXTs could not be acquired from the RM.
An error occurs while retrieving a list of readers from the RM.
Or the status thread fails to start.
Arguments:
hWnd -- The monitor's owner's HWND, for sending notification messages.
uiMsg -- The message that will be posted to the hWnd of the monitor's owner
when a change in status has occurred.
szGroupNames -- Reader groups we're interested in. see "winscard.h"
Return Value:
LONG.
Author:
Amanda Matlosz 02/26/98
Notes:
If already started, causes a restart.
All the arguments are required.
--*/
LONG CScStatusMonitor::Start(HWND hWnd, UINT uiMsg, LPCTSTR szGroupNames)
{
LONG lReturn = SCARD_S_SUCCESS;
// the monitor must be uninitialized or stopped before it can start
if (uninitialized != m_status && stopped != m_status)
{
Stop();
}
if (NULL == hWnd || 0 == uiMsg)
{
// invalid parameters
m_status = uninitialized;
return ERROR_INVALID_PARAMETER;
}
m_hwnd = hWnd;
m_uiStatusChangeMsg = uiMsg;
if (NULL == szGroupNames || 0 == _tcslen(szGroupNames))
{
m_strGroupNames = SCARD_DEFAULT_READERS;
}
//
// Get two contexts from the resource manager to use,
// one for the monitor itself, and one for it's status-watching thread
//
m_hContext = NULL;
m_hInternalContext = NULL;
lReturn = SCardEstablishContext(SCARD_SCOPE_USER,
NULL,
NULL,
&m_hContext);
if (SCARD_S_SUCCESS == lReturn)
{
lReturn = SCardEstablishContext(SCARD_SCOPE_USER,
NULL,
NULL,
&m_hInternalContext);
}
if (SCARD_S_SUCCESS != lReturn)
{
m_status = no_service;
if (NULL != m_hContext)
{
SCardReleaseContext(m_hContext);
m_hContext = NULL;
}
if (NULL != m_hInternalContext)
{
SCardReleaseContext(m_hInternalContext);
m_hInternalContext = NULL;
}
}
//
// If we successfully got a context, go ahead and initialize
// the internal reader status array & kick off status thread
//
else
{
lReturn = InitInternalReaderStatus();
}
if (SCARD_S_SUCCESS == lReturn)
{
m_status = running;
// kick off status thread
m_pStatusThrd = AfxBeginThread((AFX_THREADPROC)ScStatusChangeProc,
(LPVOID)this,
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
if (NULL == m_pStatusThrd)
{
m_status = stopped;
return GetLastError();
}
m_pStatusThrd->m_bAutoDelete = FALSE; // don't delete the thread on completion
m_pStatusThrd->ResumeThread();
}
return lReturn;
}
/*++
Stop:
In order to stop the monitor, the SCARDCONTEXTS are canceled, the
status thread is shut down, and data members that are only valid while
running are cleaned up.
Arguments:
None.
Return Value:
None.
Author:
Amanda Matlosz 02/26/98
Notes:
--*/
void CScStatusMonitor::Stop()
{
m_status = stopped;
// tell thread to stop now
SCardCancel(m_hInternalContext);
if (NULL != m_pStatusThrd)
{
DWORD dwRet = WaitForSingleObject(m_pStatusThrd->m_hThread, INFINITE); // for testing: 10000
_ASSERTE(WAIT_OBJECT_0 == dwRet);
delete m_pStatusThrd;
m_pStatusThrd = NULL;
}
// clear out internal scardcontext
SCardReleaseContext(m_hInternalContext);
m_hInternalContext = NULL;
// Empty external readerstatusarray
EmptyExternalReaderStatus();
// Empty internal readerstatusarray
if (NULL != m_pInternalReaderStatus)
{
delete[] m_pInternalReaderStatus;
m_pInternalReaderStatus = NULL;
}
m_dwInternalNumReaders = 0;
// Close main scardcontext so nothing can happen 'til restart
SCardReleaseContext(m_hContext);
m_hContext = NULL;
}
/*++
EmptyExternalReaderStatus:
This empties out the external CSCardReaderStateArray, deleting
all CSCardReaderState objects it has pointers to.
Arguments:
None.
Return Value:
None.
Author:
Amanda Matlosz 02/26/98
Notes:
--*/
void CScStatusMonitor::EmptyExternalReaderStatus(void)
{
for (int nIndex = (int)m_aReaderStatus.GetUpperBound(); 0 <= nIndex; nIndex--)
{
delete m_aReaderStatus[nIndex];
}
m_aReaderStatus.RemoveAll();
}
/*++
GetReaderStatus:
This returns copies of the Readers (CSCardReaderState) in the
"external" array.
It is assumed that the user is handing us an empty
CSCardReaderStateArray, or one that is safe to be emptied.
Arguments:
aReaderStatus -- a reference to a CSCardReaderStateArray that will
receive the values of the new array. If it is not empty, all the objects
pointed to will be deleted and removed.
Return Value:
None.
Author:
Amanda Matlosz 02/26/98
Notes:
--*/
void CScStatusMonitor::GetReaderStatus(CSCardReaderStateArray& aReaderStatus)
{
m_csRdrStsLock.Lock();
// Make sure they gave us an empty array;
// empty it out for them politely if they didn't.
if (0 != aReaderStatus.GetSize())
{
for (int i = (int)aReaderStatus.GetUpperBound(); i>=0; i--)
{
delete aReaderStatus[i];
}
aReaderStatus.RemoveAll();
}
// build external copy of internal readerstatusarray
CSCardReaderState* pReader = NULL;
for (int i = 0; i <= m_aReaderStatus.GetUpperBound(); i++)
{
pReader = new CSCardReaderState(m_aReaderStatus[i]);
ASSERT(NULL != pReader); // otherwise, fudge
if (NULL != pReader)
{
aReaderStatus.Add(pReader);
}
}
m_csRdrStsLock.Unlock();
}
/*++
SetReaderStatus:
The external ReaderStatus array is set to mirror the internal
READERSTATUSARRAY, with some embellishment.
If the external ReaderStatus array is empty, it will be built.
It if it not empty, it is assumed to be the correct length.
The CScStatusMonitor's parent is notified before returning.
Arguments:
None.
Return Value:
None.
Author:
Amanda Matlosz 02/26/98
Notes:
--*/
void CScStatusMonitor::SetReaderStatus()
{
m_csRdrStsLock.Lock();
long lReturn = SCARD_S_SUCCESS;
CSCardReaderState* pReader = NULL;
//
// if ext readerstatusarray is empty, initialize it
//
if (0 == m_aReaderStatus.GetSize())
{
for (DWORD dwIndex=0; dwIndex<m_dwInternalNumReaders; dwIndex++)
{
pReader = new CSCardReaderState();
ASSERT(NULL != pReader); // If not, fudge.
if (NULL != pReader)
{
pReader->strReader = (LPCTSTR)m_pInternalReaderStatus[dwIndex].szReader;
pReader->dwCurrentState = m_pInternalReaderStatus[dwIndex].dwCurrentState;
pReader->dwEventState = m_pInternalReaderStatus[dwIndex].dwEventState;
pReader->cbAtr = 0;
pReader->strCard = _T("");
pReader->dwState = 0;
m_aReaderStatus.Add(pReader);
}
}
pReader = NULL;
}
//
// Set everything in the external array to match the internal.
// It's safe to assume that both the internal and external
// arrays match reader for reader.
//
for (DWORD dwIndex=0; dwIndex<m_dwInternalNumReaders; dwIndex++)
{
pReader = m_aReaderStatus.GetAt(dwIndex);
bool fNewCard = false;
if (NULL == pReader)
{
ASSERT(FALSE); // this should be initialized at this point!
TRACE(_T("CScStatusMonitor::SetReaderStatus external array does not match internal array."));
break;
}
// set state
pReader->dwEventState = m_pInternalReaderStatus[dwIndex].dwEventState;
pReader->dwCurrentState = m_pInternalReaderStatus[dwIndex].dwCurrentState;
// NO CARD
if(pReader->dwEventState & SCARD_STATE_EMPTY)
{
pReader->dwState = SC_STATUS_NO_CARD;
}
// CARD in reader: SHARED, EXCLUSIVE, FREE, UNKNOWN ?
else if(pReader->dwEventState & SCARD_STATE_PRESENT)
{
if (pReader->dwEventState & SCARD_STATE_MUTE)
{
pReader->dwState = SC_STATUS_UNKNOWN;
}
else if (pReader->dwEventState & SCARD_STATE_INUSE)
{
if(pReader->dwEventState & SCARD_STATE_EXCLUSIVE)
{
pReader->dwState = SC_STATUS_EXCLUSIVE;
}
else
{
pReader->dwState = SC_STATUS_SHARED;
}
}
else
{
pReader->dwState = SC_SATATUS_AVAILABLE;
}
}
// READER ERROR: at this point, something's gone wrong
else // m_ReaderState.dwEventState & SCARD_STATE_UNAVAILABLE
{
pReader->dwState = SC_STATUS_ERROR;
}
//
// ATR and CardName: reset to empty if card is not available/responding
// else, query RM for first card name to match ATR
//
if (SC_STATUS_NO_CARD == pReader->dwState ||
SC_STATUS_UNKNOWN == pReader->dwState ||
SC_STATUS_ERROR == pReader->dwState )
{
pReader->strCard.Empty();
pReader->cbAtr = 0;
}
else
{
LPTSTR szCardName = NULL;
DWORD dwNumChar = SCARD_AUTOALLOCATE;
pReader->cbAtr = m_pInternalReaderStatus[dwIndex].cbAtr;
memcpy(pReader->rgbAtr,
m_pInternalReaderStatus[dwIndex].rgbAtr,
m_pInternalReaderStatus[dwIndex].cbAtr);
lReturn = SCardListCards(m_hInternalContext,
(LPCBYTE)pReader->rgbAtr,
NULL,
(DWORD)0,
(LPTSTR)&szCardName,
&dwNumChar);
if (SCARD_S_SUCCESS == lReturn)
{
pReader->strCard = (LPCTSTR)szCardName;
SCardFreeMemory(m_hInternalContext, (LPVOID)szCardName);
}
else
{
pReader->strCard.Empty();
}
}
} // Now the two arrays are in sync
m_csRdrStsLock.Unlock();
::PostMessage(m_hwnd, m_uiStatusChangeMsg, 0, (LONG)lReturn);
}
/*++
InitInternalReaderStatus:
This resets the internal READERSTATUSARRAY to <empty> before calling
SCardListReaders; if there are no readers, the array will remain empty;
if the RM is down, an error will be returned.
Arguments:
None.
Return Value:
0 on success; WIN32 error message otherwise.
Author:
Amanda Matlosz 02/26/98
Notes:
--*/
LONG CScStatusMonitor::InitInternalReaderStatus()
{
LONG lReturn = SCARD_S_SUCCESS;
//
// Get list of readers from Resource manager
//
if (NULL != m_pInternalReaderStatus)
{
delete[] m_pInternalReaderStatus;
}
DWORD dwNameLength = SCARD_AUTOALLOCATE;
m_szReaderNames = NULL;
m_dwInternalNumReaders = 0;
lReturn = SCardListReaders(m_hContext,
(LPTSTR)(LPCTSTR)m_strGroupNames,
(LPTSTR)&m_szReaderNames,
&dwNameLength);
if(SCARD_S_SUCCESS == lReturn)
{
// make a readerstatusarray big enough for all readers
m_dwInternalNumReaders = MStringCount(m_szReaderNames);
_ASSERTE(0 != m_dwInternalNumReaders);
m_pInternalReaderStatus = new SCARD_READERSTATE[m_dwInternalNumReaders];
if (NULL != m_pInternalReaderStatus)
{
// use the list of readers to build a readerstate array
LPCTSTR pchReader = m_szReaderNames;
int nIndex = 0;
while(0 != *pchReader)
{
m_pInternalReaderStatus[nIndex].szReader = pchReader;
m_pInternalReaderStatus[nIndex].dwCurrentState = SCARD_STATE_UNAWARE;
pchReader += lstrlen(pchReader)+1;
nIndex++;
}
}
else
{
lReturn = SCARD_E_NO_MEMORY;
}
}
else if (SCARD_E_NO_READERS_AVAILABLE == lReturn)
{
m_status = no_readers;
if(NULL != m_szReaderNames)
{
SCardFreeMemory(m_hContext, (LPVOID)m_szReaderNames);
m_szReaderNames = NULL;
}
}
// else m_status == unknown?
// this array, and the m_szReaderNames used to build it, are now property of
// the StatusChangeProc...
return lReturn;
}
/*++
ScStatusChangeProc:
Arguments:
pParam - CScStatusMonitor*
Return Value:
0 on success; WIN32 error message otherwise.
Author:
Amanda Matlosz 02/26/98
Notes:
--*/
UINT ScStatusChangeProc(LPVOID pParam)
{
UINT uiReturn = 0;
if(NULL != pParam)
{
return ((CScStatusMonitor*)pParam)->GetStatusChangeProc();
}
return SCARD_E_INVALID_PARAMETER;
}
UINT CScStatusMonitor::GetStatusChangeProc()
{
LONG lReturn = SCARD_S_SUCCESS;
while (stopped != m_status)
{
// Wait for change in status (safe to use pMonitor's internal vars)
lReturn = SCardGetStatusChange(m_hInternalContext,
INFINITE,
m_pInternalReaderStatus,
m_dwInternalNumReaders);
// inform monitor that given status has changed (Only on success!)
if (SCARD_S_SUCCESS == lReturn)
{
SetReaderStatus();
}
else
{
//
// If the context has been cancelled, quit quietly
// Otherwise, announce that the thread is aborting prematurely
//
m_status = stopped;
if(SCARD_E_CANCELLED != lReturn)
{
// TODO: ? wrap in critsec ?
m_pStatusThrd = NULL;
// TODO: ? end crit sec ?
::PostMessage(m_hwnd, m_uiStatusChangeMsg, 0, (LONG)lReturn);
}
break;
}
// Prep the array for the next GetStatusChange call
for(DWORD dwIndex=0; dwIndex<m_dwInternalNumReaders; dwIndex++)
{
m_pInternalReaderStatus[dwIndex].dwCurrentState =
m_pInternalReaderStatus[dwIndex].dwEventState;
}
}
// Clean Up
if(NULL != m_szReaderNames)
{
SCardFreeMemory(m_hContext, (LPVOID)m_szReaderNames);
m_szReaderNames = NULL;
}
return (UINT)0;
}