use a separate directory for ifd-vpcd and pcsclite-vpcd
This commit is contained in:
601
virtualsmartcard/src/pcsclite-vpcd/winscard.c
Normal file
601
virtualsmartcard/src/pcsclite-vpcd/winscard.c
Normal file
@@ -0,0 +1,601 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Frank Morgner
|
||||
*
|
||||
* This file is part of virtualsmartcard.
|
||||
*
|
||||
* virtualsmartcard 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.
|
||||
*
|
||||
* virtualsmartcard 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
|
||||
* virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include "ifd-vpcd.h"
|
||||
#include "vpcd.h"
|
||||
#include <ifdhandler.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <winscard.h>
|
||||
|
||||
struct card {
|
||||
DWORD dwShareMode;
|
||||
size_t usage_counter;
|
||||
};
|
||||
|
||||
#define SET_R_TEST(value) { r = value; if (r != SCARD_S_SUCCESS) { goto err; } }
|
||||
|
||||
static struct card cards[PCSCLITE_MAX_READERS_CONTEXTS];
|
||||
static int cancel_status = 0;
|
||||
static size_t context_count = 0;
|
||||
|
||||
static const char reader_format_str[] = "Virtual PCD %02"SCNu32;
|
||||
static const SCARDHANDLE validhandle = 1;
|
||||
|
||||
/* defined as "extern" in pcsclite.h, but not used here */
|
||||
const SCARD_IO_REQUEST g_rgSCardT0Pci, g_rgSCardT1Pci, g_rgSCardRawPci;
|
||||
|
||||
/* part of libvpcd, but not exported */
|
||||
extern const unsigned char vicc_max_slots;
|
||||
extern const char *hostname;
|
||||
|
||||
static LONG autoallocate(void *buf, LPDWORD len, DWORD max, void **rbuf)
|
||||
{
|
||||
LPSTR *p;
|
||||
|
||||
if (!len || !rbuf)
|
||||
return SCARD_E_INVALID_PARAMETER;
|
||||
|
||||
if (buf && (*len == SCARD_AUTOALLOCATE)) {
|
||||
p = malloc(max);
|
||||
if (!p)
|
||||
return SCARD_E_NO_MEMORY;
|
||||
|
||||
/* copy the pointer */
|
||||
memcpy(buf, &p, sizeof p);
|
||||
|
||||
*len = max;
|
||||
*rbuf = p;
|
||||
} else {
|
||||
/* we don't have anything to do.
|
||||
* Simply tell the caller to use buf with unchanged length */
|
||||
*rbuf = buf;
|
||||
}
|
||||
|
||||
return SCARD_S_SUCCESS;
|
||||
}
|
||||
|
||||
static void initialize_globals(void)
|
||||
{
|
||||
DWORD Lun, Channel = VPCDPORT;
|
||||
const char *hostname_old = hostname;
|
||||
|
||||
hostname = VPCDHOST;
|
||||
for (Lun = 0;
|
||||
Lun < PCSCLITE_MAX_READERS_CONTEXTS && Lun < vicc_max_slots;
|
||||
Lun++) {
|
||||
IFDHCreateChannel (Lun, Channel);
|
||||
}
|
||||
hostname = hostname_old;
|
||||
}
|
||||
|
||||
static void release_globals(void)
|
||||
{
|
||||
DWORD Lun;
|
||||
for (Lun = 0;
|
||||
Lun < PCSCLITE_MAX_READERS_CONTEXTS && Lun < vicc_max_slots;
|
||||
Lun++) {
|
||||
IFDHCloseChannel (Lun);
|
||||
}
|
||||
memset(cards, 0, sizeof cards);
|
||||
}
|
||||
|
||||
static LONG handle2card(SCARDHANDLE hCard, struct card **card)
|
||||
{
|
||||
DWORD index = hCard;
|
||||
|
||||
if (!card)
|
||||
return SCARD_F_INTERNAL_ERROR;
|
||||
|
||||
if (index >= PCSCLITE_MAX_READERS_CONTEXTS)
|
||||
return SCARD_E_INVALID_HANDLE;
|
||||
|
||||
*card = &cards[index];
|
||||
|
||||
return SCARD_S_SUCCESS;
|
||||
}
|
||||
|
||||
LONG handle2reader(DWORD index, LPSTR mszReaderName, LPDWORD pcchReaderLen)
|
||||
{
|
||||
LONG r;
|
||||
char *reader;
|
||||
int length;
|
||||
|
||||
if (!pcchReaderLen) {
|
||||
r = SCARD_E_INVALID_PARAMETER;
|
||||
goto err;
|
||||
}
|
||||
|
||||
SET_R_TEST( autoallocate(mszReaderName, pcchReaderLen, MAX_READERNAME, (void **) &reader));
|
||||
|
||||
if (reader) {
|
||||
/* caller wants to have the string */
|
||||
length = snprintf(reader, *pcchReaderLen, reader_format_str, (uint32_t) index);
|
||||
if (length > *pcchReaderLen) {
|
||||
r = SCARD_E_INSUFFICIENT_BUFFER;
|
||||
goto err;
|
||||
}
|
||||
} else {
|
||||
/* caller wants to have the length */
|
||||
length = snprintf(reader, 0, reader_format_str, (uint32_t) index);
|
||||
}
|
||||
|
||||
if (length < 0) {
|
||||
r = SCARD_F_INTERNAL_ERROR;
|
||||
goto err;
|
||||
}
|
||||
|
||||
*pcchReaderLen = length;
|
||||
/* r has already been set to SCARD_S_SUCCESS */
|
||||
|
||||
err:
|
||||
return r;
|
||||
}
|
||||
|
||||
static LONG reader2card(LPCSTR szReader, struct card **card, LPSCARDHANDLE phCard)
|
||||
{
|
||||
DWORD index;
|
||||
|
||||
if (!card)
|
||||
return SCARD_F_INTERNAL_ERROR;
|
||||
|
||||
if (1 != sscanf(szReader, reader_format_str, &index)
|
||||
|| index >= PCSCLITE_MAX_READERS_CONTEXTS)
|
||||
return SCARD_E_READER_UNAVAILABLE;
|
||||
|
||||
*card = &cards[index];
|
||||
if (phCard)
|
||||
*phCard = index;
|
||||
|
||||
return SCARD_S_SUCCESS;
|
||||
}
|
||||
|
||||
static LONG responsecode2long(RESPONSECODE r)
|
||||
{
|
||||
switch (r) {
|
||||
case IFD_ICC_PRESENT:
|
||||
case IFD_SUCCESS:
|
||||
return SCARD_S_SUCCESS;
|
||||
case IFD_ERROR_INSUFFICIENT_BUFFER:
|
||||
return SCARD_E_INSUFFICIENT_BUFFER;
|
||||
case IFD_ERROR_NOT_SUPPORTED:
|
||||
case IFD_NOT_SUPPORTED:
|
||||
case IFD_PROTOCOL_NOT_SUPPORTED:
|
||||
return SCARD_E_UNSUPPORTED_FEATURE;
|
||||
case IFD_ERROR_CONFISCATE:
|
||||
case IFD_ERROR_EJECT:
|
||||
case IFD_ERROR_POWER_ACTION:
|
||||
return SCARD_E_CANT_DISPOSE;
|
||||
case IFD_RESPONSE_TIMEOUT:
|
||||
return SCARD_E_TIMEOUT;
|
||||
case IFD_ICC_NOT_PRESENT:
|
||||
return SCARD_E_NO_SMARTCARD;
|
||||
case IFD_COMMUNICATION_ERROR:
|
||||
return SCARD_F_COMM_ERROR;
|
||||
case IFD_NO_SUCH_DEVICE:
|
||||
return SCARD_E_READER_UNAVAILABLE;
|
||||
case IFD_ERROR_TAG:
|
||||
case IFD_ERROR_SET_FAILURE:
|
||||
case IFD_ERROR_VALUE_READ_ONLY:
|
||||
case IFD_ERROR_PTS_FAILURE:
|
||||
case IFD_ERROR_SWALLOW:
|
||||
default:
|
||||
return SCARD_F_INTERNAL_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
static LONG handle2atr(DWORD Lun, LPBYTE pbAtr, LPDWORD pcbAtrLen)
|
||||
{
|
||||
LONG r;
|
||||
void *atr;
|
||||
unsigned char _atr[MAX_ATR_SIZE];
|
||||
|
||||
SET_R_TEST( responsecode2long(
|
||||
IFDHICCPresence(Lun)));
|
||||
|
||||
SET_R_TEST( autoallocate(pbAtr, pcbAtrLen, MAX_ATR_SIZE, (void **) &atr));
|
||||
|
||||
if (!atr) {
|
||||
/* caller wants to have the length */
|
||||
*pcbAtrLen = sizeof _atr;
|
||||
atr = _atr;
|
||||
}
|
||||
|
||||
SET_R_TEST( responsecode2long(
|
||||
IFDHGetCapabilities (Lun, TAG_IFD_ATR, pcbAtrLen, atr)));
|
||||
|
||||
err:
|
||||
return r;
|
||||
}
|
||||
|
||||
PCSC_API LONG SCardEstablishContext(DWORD dwScope, LPCVOID pvReserved1, LPCVOID pvReserved2, LPSCARDCONTEXT phContext)
|
||||
{
|
||||
if (!phContext)
|
||||
return SCARD_E_INVALID_PARAMETER;
|
||||
|
||||
if (!context_count)
|
||||
initialize_globals();
|
||||
context_count++;
|
||||
|
||||
*phContext = validhandle;
|
||||
|
||||
return SCARD_S_SUCCESS;
|
||||
}
|
||||
|
||||
PCSC_API LONG SCardReleaseContext(SCARDCONTEXT hContext)
|
||||
{
|
||||
if (context_count) {
|
||||
context_count--;
|
||||
}
|
||||
|
||||
if (!context_count) {
|
||||
release_globals();
|
||||
}
|
||||
|
||||
return SCARD_S_SUCCESS;
|
||||
}
|
||||
|
||||
PCSC_API LONG SCardIsValidContext(SCARDCONTEXT hContext)
|
||||
{
|
||||
if (context_count && hContext == validhandle)
|
||||
return SCARD_S_SUCCESS;
|
||||
else
|
||||
return SCARD_E_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
PCSC_API LONG SCardSetTimeout(SCARDCONTEXT hContext, DWORD dwTimeout)
|
||||
{
|
||||
/* XXX better return an error here? */
|
||||
return SCARD_S_SUCCESS;
|
||||
}
|
||||
|
||||
PCSC_API LONG SCardConnect(SCARDCONTEXT hContext, LPCSTR szReader, DWORD dwShareMode, DWORD dwPreferredProtocols, LPSCARDHANDLE phCard, LPDWORD pdwActiveProtocol)
|
||||
{
|
||||
struct card *card;
|
||||
LONG r;
|
||||
|
||||
SET_R_TEST( reader2card(szReader, &card, phCard));
|
||||
|
||||
if (card->usage_counter) {
|
||||
/* card/reader already in use */
|
||||
if (card->dwShareMode == SCARD_SHARE_EXCLUSIVE
|
||||
|| card->dwShareMode != dwShareMode) {
|
||||
/* cannot use the provided mode */
|
||||
return SCARD_E_SHARING_VIOLATION;
|
||||
}
|
||||
}
|
||||
|
||||
card->usage_counter++;
|
||||
card->dwShareMode = dwShareMode;
|
||||
|
||||
err:
|
||||
return r;
|
||||
}
|
||||
|
||||
PCSC_API LONG SCardReconnect(SCARDHANDLE hCard, DWORD dwShareMode, DWORD dwPreferredProtocols, DWORD dwInitialization, LPDWORD pdwActiveProtocol)
|
||||
{
|
||||
struct card *card;
|
||||
LONG r;
|
||||
|
||||
SET_R_TEST( handle2card(hCard, &card));
|
||||
|
||||
if (card->usage_counter > 1
|
||||
&& card->dwShareMode != dwShareMode) {
|
||||
/* cannot use the provided mode */
|
||||
r = SCARD_E_SHARING_VIOLATION;
|
||||
goto err;
|
||||
}
|
||||
|
||||
card->dwShareMode = dwShareMode;
|
||||
|
||||
err:
|
||||
return r;
|
||||
}
|
||||
|
||||
PCSC_API LONG SCardDisconnect(SCARDHANDLE hCard, DWORD dwDisposition)
|
||||
{
|
||||
DWORD Lun = hCard;
|
||||
LONG r;
|
||||
UCHAR Atr[MAX_ATR_SIZE];
|
||||
DWORD AtrLength = sizeof Atr;
|
||||
struct card *card;
|
||||
|
||||
SET_R_TEST( handle2card(hCard, &card));
|
||||
|
||||
if (!card->usage_counter) {
|
||||
r = SCARD_E_INVALID_HANDLE;
|
||||
goto err;
|
||||
}
|
||||
card->usage_counter--;
|
||||
|
||||
switch (dwDisposition) {
|
||||
case SCARD_LEAVE_CARD:
|
||||
r = SCARD_S_SUCCESS;
|
||||
break;
|
||||
|
||||
case SCARD_RESET_CARD:
|
||||
if (card->usage_counter) {
|
||||
r = SCARD_E_CANT_DISPOSE;
|
||||
goto err;
|
||||
}
|
||||
SET_R_TEST( responsecode2long(
|
||||
IFDHPowerICC (Lun, IFD_RESET, Atr, &AtrLength)));
|
||||
break;
|
||||
|
||||
case SCARD_EJECT_CARD:
|
||||
/* fall through */
|
||||
case SCARD_UNPOWER_CARD:
|
||||
if (card->usage_counter) {
|
||||
r = SCARD_E_CANT_DISPOSE;
|
||||
goto err;
|
||||
}
|
||||
SET_R_TEST( responsecode2long(
|
||||
IFDHPowerICC (Lun, IFD_POWER_DOWN, Atr, &AtrLength)));
|
||||
break;
|
||||
|
||||
default:
|
||||
r = SCARD_E_INVALID_PARAMETER;
|
||||
break;
|
||||
}
|
||||
|
||||
err:
|
||||
return r;
|
||||
}
|
||||
|
||||
PCSC_API LONG SCardBeginTransaction(SCARDHANDLE hCard)
|
||||
{
|
||||
struct card *card;
|
||||
LONG r;
|
||||
|
||||
SET_R_TEST( handle2card(hCard, &card));
|
||||
|
||||
/* we don't have a strategy if an other card handle is active */
|
||||
if (card->usage_counter != 1)
|
||||
return SCARD_E_SHARING_VIOLATION;
|
||||
|
||||
card->dwShareMode = SCARD_SHARE_EXCLUSIVE;
|
||||
|
||||
err:
|
||||
return r;
|
||||
}
|
||||
|
||||
PCSC_API LONG SCardEndTransaction(SCARDHANDLE hCard, DWORD dwDisposition)
|
||||
{
|
||||
struct card *card;
|
||||
LONG r;
|
||||
|
||||
SET_R_TEST( handle2card(hCard, &card));
|
||||
|
||||
card->dwShareMode = dwDisposition;
|
||||
|
||||
err:
|
||||
return r;
|
||||
}
|
||||
|
||||
PCSC_API LONG SCardCancelTransaction(SCARDHANDLE hCard)
|
||||
{
|
||||
return SCARD_S_SUCCESS;
|
||||
}
|
||||
|
||||
PCSC_API LONG SCardStatus(SCARDHANDLE hCard, LPSTR mszReaderName, LPDWORD pcchReaderLen, LPDWORD pdwState, LPDWORD pdwProtocol, LPBYTE pbAtr, LPDWORD pcbAtrLen)
|
||||
{
|
||||
LONG r;
|
||||
|
||||
SET_R_TEST( handle2reader(hCard, mszReaderName, pcchReaderLen));
|
||||
SET_R_TEST( handle2atr(hCard, pbAtr, pcbAtrLen));
|
||||
|
||||
err:
|
||||
return r;
|
||||
}
|
||||
|
||||
PCSC_API LONG SCardGetStatusChange(SCARDCONTEXT hContext, DWORD dwTimeout, LPSCARD_READERSTATE rgReaderStates, DWORD cReaders)
|
||||
{
|
||||
SCARDHANDLE hCard;
|
||||
size_t i, seconds, event_count = 0;
|
||||
struct card *card;
|
||||
|
||||
cancel_status = 0;
|
||||
|
||||
if (dwTimeout == INFINITE) {
|
||||
/* "infinite" timeout */
|
||||
seconds = 60;
|
||||
} else {
|
||||
seconds = dwTimeout/1000;
|
||||
}
|
||||
|
||||
do {
|
||||
for (i = 0; i < cReaders; i++) {
|
||||
if (rgReaderStates[i].dwCurrentState & SCARD_STATE_IGNORE)
|
||||
/* this reader should be ignored */
|
||||
continue;
|
||||
|
||||
if (strcmp(rgReaderStates[i].szReader, "\\\\?PnP?\\Notification") == 0)
|
||||
/* we don't allow readers to be added or removed */
|
||||
continue;
|
||||
|
||||
rgReaderStates[i].dwEventState = 0;
|
||||
|
||||
if (SCARD_S_SUCCESS != reader2card(rgReaderStates[i].szReader,
|
||||
&card, &hCard)) {
|
||||
/* given reader not recognized */
|
||||
rgReaderStates[i].dwEventState |= SCARD_STATE_UNKNOWN
|
||||
| SCARD_STATE_CHANGED |SCARD_STATE_IGNORE;
|
||||
event_count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (card->usage_counter) {
|
||||
rgReaderStates[i].dwEventState |= SCARD_STATE_INUSE;
|
||||
if (card->dwShareMode == SCARD_SHARE_EXCLUSIVE)
|
||||
rgReaderStates[i].dwEventState |= SCARD_STATE_EXCLUSIVE;
|
||||
}
|
||||
|
||||
/* normally the application should set cbAtr appropriately.
|
||||
* Some application don't mind to do that (e.g., pcsc_scan) */
|
||||
rgReaderStates[i].cbAtr = sizeof rgReaderStates[i].rgbAtr;
|
||||
|
||||
if (SCARD_S_SUCCESS != handle2atr(hCard, rgReaderStates[i].rgbAtr,
|
||||
&rgReaderStates[i].cbAtr)) {
|
||||
rgReaderStates[i].dwEventState |= SCARD_STATE_EMPTY;
|
||||
rgReaderStates[i].cbAtr = 0;
|
||||
} else {
|
||||
rgReaderStates[i].dwEventState |= SCARD_STATE_PRESENT;
|
||||
}
|
||||
|
||||
/* if current and event state differ in the flags SCARD_STATE_EMPTY
|
||||
* or SCARD_STATE_PRESENT a state change has occurred */
|
||||
if (((rgReaderStates[i].dwCurrentState & SCARD_STATE_EMPTY)
|
||||
!= (rgReaderStates[i].dwEventState & SCARD_STATE_EMPTY))
|
||||
|| ((rgReaderStates[i].dwCurrentState & SCARD_STATE_PRESENT)
|
||||
!= (rgReaderStates[i].dwEventState & SCARD_STATE_PRESENT))) {
|
||||
rgReaderStates[i].dwEventState |= SCARD_STATE_CHANGED;
|
||||
event_count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!seconds || event_count)
|
||||
break;
|
||||
|
||||
sleep(1);
|
||||
seconds--;
|
||||
|
||||
} while (!cancel_status);
|
||||
|
||||
if (!event_count)
|
||||
return SCARD_E_TIMEOUT;
|
||||
|
||||
return SCARD_S_SUCCESS;
|
||||
}
|
||||
|
||||
PCSC_API LONG SCardCancel(SCARDHANDLE hCard)
|
||||
{
|
||||
cancel_status = 1;
|
||||
return SCARD_S_SUCCESS;
|
||||
}
|
||||
|
||||
PCSC_API LONG SCardControl(SCARDHANDLE hCard, DWORD dwControlCode, LPCVOID pbSendBuffer, DWORD cbSendLength, LPVOID pbRecvBuffer, DWORD cbRecvLength, LPDWORD lpBytesReturned)
|
||||
{
|
||||
return responsecode2long(
|
||||
IFDHControl (hCard, dwControlCode, (PUCHAR) pbSendBuffer,
|
||||
cbSendLength, pbRecvBuffer, cbRecvLength, lpBytesReturned));
|
||||
}
|
||||
|
||||
PCSC_API LONG SCardTransmit(SCARDHANDLE hCard, LPCSCARD_IO_REQUEST pioSendPci, LPCBYTE pbSendBuffer, DWORD cbSendLength, LPSCARD_IO_REQUEST pioRecvPci, LPBYTE pbRecvBuffer, LPDWORD pcbRecvLength)
|
||||
{
|
||||
DWORD Lun = hCard;
|
||||
LONG r;
|
||||
/* ignored */
|
||||
SCARD_IO_HEADER SendPci, RecvPci;
|
||||
|
||||
/* transceive data */
|
||||
SET_R_TEST( responsecode2long(
|
||||
IFDHTransmitToICC (Lun, SendPci, (PUCHAR) pbSendBuffer,
|
||||
cbSendLength, pbRecvBuffer, pcbRecvLength, &RecvPci)));
|
||||
|
||||
err:
|
||||
return r;
|
||||
}
|
||||
|
||||
PCSC_API LONG SCardGetAttrib(SCARDHANDLE hCard, DWORD dwAttrId, LPBYTE pbAttr, LPDWORD pcbAttrLen)
|
||||
{
|
||||
return SCARD_F_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
PCSC_API LONG SCardSetAttrib(SCARDHANDLE hCard, DWORD dwAttrId, LPCBYTE pbAttr, DWORD cbAttrLen)
|
||||
{
|
||||
return SCARD_F_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
PCSC_API LONG SCardListReaders(SCARDCONTEXT hContext, LPCSTR mszGroups, LPSTR mszReaders, LPDWORD pcchReaders)
|
||||
{
|
||||
DWORD Lun;
|
||||
DWORD readerslen = 0, readerlen;
|
||||
LONG r;
|
||||
char *readers;
|
||||
|
||||
if (!pcchReaders) {
|
||||
r = SCARD_E_INVALID_PARAMETER;
|
||||
goto err;
|
||||
}
|
||||
|
||||
|
||||
SET_R_TEST( autoallocate(mszReaders, pcchReaders,
|
||||
(MAX_READERNAME+1)*PCSCLITE_MAX_READERS_CONTEXTS,
|
||||
(void **) &readers));
|
||||
|
||||
|
||||
/* write reader names */
|
||||
for (Lun = 0; Lun < PCSCLITE_MAX_READERS_CONTEXTS; Lun++) {
|
||||
/* what memory we have left */
|
||||
readerlen = *pcchReaders - readerslen;
|
||||
|
||||
/* get the current readername */
|
||||
SET_R_TEST( handle2reader(Lun, readers ? readers+readerslen : NULL,
|
||||
&readerlen));
|
||||
|
||||
/* readerlen has been set to the correct value */
|
||||
readerslen += readerlen;
|
||||
/* copy null character */
|
||||
readerslen++;
|
||||
}
|
||||
|
||||
*pcchReaders = readerslen;
|
||||
|
||||
if (!readerslen) {
|
||||
r = SCARD_E_NO_READERS_AVAILABLE;
|
||||
} else {
|
||||
r = SCARD_S_SUCCESS;
|
||||
}
|
||||
|
||||
err:
|
||||
return r;
|
||||
}
|
||||
|
||||
PCSC_API LONG SCardListReaderGroups(SCARDCONTEXT hContext, LPSTR mszGroups, LPDWORD pcchGroups)
|
||||
{
|
||||
LONG r;
|
||||
unsigned char *groups;
|
||||
|
||||
SET_R_TEST( autoallocate(mszGroups, pcchGroups, 1, (void **) &groups));
|
||||
|
||||
if (groups) {
|
||||
/* caller wants to have the string */
|
||||
*groups = '\0';
|
||||
} else {
|
||||
/* caller wants to have the length */
|
||||
}
|
||||
|
||||
*pcchGroups = 1;
|
||||
|
||||
|
||||
err:
|
||||
return r;
|
||||
}
|
||||
|
||||
PCSC_API LONG SCardFreeMemory(SCARDCONTEXT hContext, LPCVOID pvMem)
|
||||
{
|
||||
free((void *) pvMem);
|
||||
|
||||
return SCARD_S_SUCCESS;
|
||||
}
|
||||
Reference in New Issue
Block a user