#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/list.h>
#include <fcntl.h>
#include <fts.h>
#include <errno.h>
#include <strings.h>
#include <unistd.h>
#include <upanic.h>
#include <sys/debug.h>
#include <sys/filio.h>
#include <sys/usb/clients/ccid/uccid.h>
#include <winscard.h>
typedef struct pcsc_hdl {
hrtime_t pcsc_create_time;
list_t pcsc_autoalloc;
list_t pcsc_cards;
} pcsc_hdl_t;
typedef struct pcsc_card {
list_node_t pcc_link;
pcsc_hdl_t *pcc_hdl;
int pcc_fd;
char *pcc_name;
size_t pcc_namelen;
} pcsc_card_t;
typedef struct pcsc_mem {
list_node_t pcm_link;
void *pcm_buf;
} pcsc_mem_t;
SCARD_IO_REQUEST g_rgSCardT0Pci = {
SCARD_PROTOCOL_T0,
0
};
SCARD_IO_REQUEST g_rgSCardT1Pci = {
SCARD_PROTOCOL_T1,
0
};
SCARD_IO_REQUEST g_rgSCardRawPci = {
SCARD_PROTOCOL_RAW,
0
};
const char *
pcsc_stringify_error(const LONG err)
{
switch (err) {
case SCARD_S_SUCCESS:
return ("no error");
case SCARD_F_INTERNAL_ERROR:
return ("internal error");
case SCARD_E_CANCELLED:
return ("request cancelled");
case SCARD_E_INVALID_HANDLE:
return ("invalid handle");
case SCARD_E_INVALID_PARAMETER:
return ("invalid parameter");
case SCARD_E_NO_MEMORY:
return ("no memory");
case SCARD_E_INSUFFICIENT_BUFFER:
return ("buffer was insufficiently sized");
case SCARD_E_INVALID_VALUE:
return ("invalid value passed");
case SCARD_E_UNKNOWN_READER:
return ("unknown reader");
case SCARD_E_TIMEOUT:
return ("timeout occurred");
case SCARD_E_SHARING_VIOLATION:
return ("sharing violation");
case SCARD_E_NO_SMARTCARD:
return ("no smartcard present");
case SCARD_E_UNKNOWN_CARD:
return ("unknown ICC");
case SCARD_E_PROTO_MISMATCH:
return ("protocol mismatch");
case SCARD_F_COMM_ERROR:
return ("communication error");
case SCARD_F_UNKNOWN_ERROR:
return ("unknown error");
case SCARD_E_READER_UNAVAILABLE:
return ("reader unavailable");
case SCARD_E_NO_SERVICE:
return ("service error");
case SCARD_E_UNSUPPORTED_FEATURE:
return ("ICC requires unsupported feature");
case SCARD_E_NO_READERS_AVAILABLE:
return ("no readers avaiable");
case SCARD_W_UNSUPPORTED_CARD:
return ("ICC unsupported");
case SCARD_W_UNPOWERED_CARD:
return ("ICC is not powered");
case SCARD_W_RESET_CARD:
return ("ICC was reset");
case SCARD_W_REMOVED_CARD:
return ("ICC has been removed");
default:
return ("unknown error");
}
}
static void *
pcsc_mem_alloc(pcsc_hdl_t *hdl, size_t len)
{
pcsc_mem_t *mem;
if ((mem = malloc(sizeof (*mem))) == NULL) {
return (NULL);
}
if ((mem->pcm_buf = malloc(len)) == NULL) {
free(mem);
return (NULL);
}
list_link_init(&mem->pcm_link);
list_insert_tail(&hdl->pcsc_autoalloc, mem);
return (mem->pcm_buf);
}
static void
pcsc_mem_free(pcsc_hdl_t *hdl, void *buf)
{
for (pcsc_mem_t *mem = list_head(&hdl->pcsc_autoalloc); mem != NULL;
mem = list_next(&hdl->pcsc_autoalloc, mem)) {
if (mem->pcm_buf == buf) {
list_remove(&hdl->pcsc_autoalloc, mem);
free(mem->pcm_buf);
free(mem);
return;
}
}
char msg[512];
(void) snprintf(msg, sizeof (msg), "freed buffer %p not in context %p",
buf, hdl);
upanic(msg, strlen(msg));
}
static pcsc_card_t *
pcsc_card_alloc(pcsc_hdl_t *hdl, const char *reader)
{
pcsc_card_t *card;
if ((card = malloc(sizeof (*card))) == NULL) {
return (NULL);
}
card->pcc_hdl = hdl;
card->pcc_fd = -1;
list_link_init(&card->pcc_link);
card->pcc_namelen = strlen(reader) + 2;
if ((card->pcc_name = malloc(card->pcc_namelen)) == NULL) {
free(card);
return (NULL);
}
bcopy(reader, card->pcc_name, card->pcc_namelen - 1);
card->pcc_name[card->pcc_namelen - 1] = '\0';
list_insert_tail(&hdl->pcsc_cards, card);
return (card);
}
static void
pcsc_card_free(pcsc_card_t *card)
{
if (card == NULL) {
return;
}
if (card->pcc_fd >= 0) {
(void) close(card->pcc_fd);
}
pcsc_hdl_t *hdl = card->pcc_hdl;
list_remove(&hdl->pcsc_cards, card);
free(card->pcc_name);
free(card);
}
LONG
SCardEstablishContext(DWORD scope, LPCVOID unused0, LPCVOID unused1,
LPSCARDCONTEXT outp)
{
pcsc_hdl_t *hdl;
if (outp == NULL) {
return (SCARD_E_INVALID_PARAMETER);
}
if (scope != SCARD_SCOPE_SYSTEM) {
return (SCARD_E_INVALID_VALUE);
}
hdl = calloc(1, sizeof (pcsc_hdl_t));
if (hdl == NULL) {
return (SCARD_E_NO_MEMORY);
}
list_create(&hdl->pcsc_autoalloc, sizeof (pcsc_mem_t),
offsetof(pcsc_mem_t, pcm_link));
list_create(&hdl->pcsc_cards, sizeof (pcsc_card_t),
offsetof(pcsc_card_t, pcc_link));
hdl->pcsc_create_time = gethrtime();
*outp = hdl;
return (SCARD_S_SUCCESS);
}
bool
pcsc_valid_context(SCARDCONTEXT hdl)
{
return (hdl != NULL && (uintptr_t)hdl != UINTPTR_MAX);
}
LONG
SCardIsValidContext(SCARDCONTEXT hdl)
{
if (!pcsc_valid_context(hdl)) {
return (SCARD_E_INVALID_HANDLE);
}
return (SCARD_S_SUCCESS);
}
LONG
SCardReleaseContext(SCARDCONTEXT arg)
{
if (!pcsc_valid_context(arg)) {
return (SCARD_E_INVALID_HANDLE);
}
pcsc_hdl_t *hdl = arg;
pcsc_mem_t *mem;
while ((mem = list_head(&hdl->pcsc_autoalloc)) != NULL) {
pcsc_mem_free(hdl, mem->pcm_buf);
}
list_destroy(&hdl->pcsc_autoalloc);
pcsc_card_t *card;
while ((card = list_head(&hdl->pcsc_cards)) != NULL) {
pcsc_card_free(card);
}
list_destroy(&hdl->pcsc_cards);
free(hdl);
return (SCARD_S_SUCCESS);
}
LONG
SCardFreeMemory(SCARDCONTEXT hdl, LPCVOID mem)
{
if (!pcsc_valid_context(hdl)) {
return (SCARD_E_INVALID_HANDLE);
}
pcsc_mem_free(hdl, (void *)mem);
return (SCARD_S_SUCCESS);
}
LONG
SCardListReaders(SCARDCONTEXT arg, LPCSTR groups, LPSTR bufp, LPDWORD lenp)
{
pcsc_hdl_t *hdl = arg;
FTS *fts;
FTSENT *ent;
char *const root[] = { "/dev/ccid", NULL };
char *ubuf;
char **readers;
uint32_t len, ulen, npaths, nalloc, off, i;
int ret;
if (!pcsc_valid_context(hdl)) {
return (SCARD_E_INVALID_HANDLE);
}
if (groups != NULL || lenp == NULL) {
return (SCARD_E_INVALID_PARAMETER);
}
fts = fts_open(root, FTS_LOGICAL | FTS_NOCHDIR, NULL);
if (fts == NULL) {
switch (errno) {
case ENOENT:
case ENOTDIR:
return (SCARD_E_NO_READERS_AVAILABLE);
case ENOMEM:
case EAGAIN:
return (SCARD_E_NO_MEMORY);
default:
return (SCARD_E_NO_SERVICE);
}
}
npaths = nalloc = 0;
len = 1;
readers = NULL;
while ((ent = fts_read(fts)) != NULL) {
size_t plen;
if (ent->fts_level != 2 || ent->fts_info == FTS_DP)
continue;
if (ent->fts_info == FTS_ERR || ent->fts_info == FTS_NS)
continue;
if (S_ISCHR(ent->fts_statp->st_mode) == 0)
continue;
plen = strlen(ent->fts_path) + 1;
if (UINT32_MAX - len <= plen) {
ret = SCARD_E_INSUFFICIENT_BUFFER;
goto out;
}
if (npaths == nalloc) {
char **tmp;
nalloc += 8;
tmp = reallocarray(readers, nalloc, sizeof (char *));
if (tmp == NULL) {
ret = SCARD_E_NO_MEMORY;
goto out;
}
readers = tmp;
}
readers[npaths] = strdup(ent->fts_path);
npaths++;
len += plen;
}
if (npaths == 0) {
ret = SCARD_E_NO_READERS_AVAILABLE;
goto out;
}
ulen = *lenp;
*lenp = len;
if (ulen != SCARD_AUTOALLOCATE) {
if (bufp == NULL) {
ret = SCARD_S_SUCCESS;
goto out;
}
if (ulen < len) {
ret = SCARD_E_INSUFFICIENT_BUFFER;
goto out;
}
ubuf = bufp;
} else {
char **bufpp;
if (bufp == NULL) {
ret = SCARD_E_INVALID_PARAMETER;
goto out;
}
if ((ubuf = pcsc_mem_alloc(hdl, ulen)) == NULL) {
ret = SCARD_E_NO_MEMORY;
goto out;
}
bufpp = (void *)bufp;
*bufpp = ubuf;
}
ret = SCARD_S_SUCCESS;
for (off = 0, i = 0; i < npaths; i++) {
size_t slen = strlen(readers[i]) + 1;
bcopy(readers[i], ubuf + off, slen);
off += slen;
VERIFY3U(off, <=, len);
}
VERIFY3U(off, ==, len - 1);
ubuf[off] = '\0';
out:
for (i = 0; i < npaths; i++) {
free(readers[i]);
}
free(readers);
(void) fts_close(fts);
return (ret);
}
static LONG
uccid_status_helper(int fd, DWORD prots, uccid_cmd_status_t *ucs)
{
bzero(ucs, sizeof (uccid_cmd_status_t));
ucs->ucs_version = UCCID_CURRENT_VERSION;
if (ioctl(fd, UCCID_CMD_STATUS, ucs) != 0) {
return (SCARD_F_UNKNOWN_ERROR);
}
if ((ucs->ucs_status & UCCID_STATUS_F_CARD_PRESENT) == 0) {
return (SCARD_W_REMOVED_CARD);
}
if ((ucs->ucs_status & UCCID_STATUS_F_CARD_ACTIVE) == 0) {
return (SCARD_W_UNPOWERED_CARD);
}
if ((ucs->ucs_status & UCCID_STATUS_F_PARAMS_VALID) == 0) {
return (SCARD_W_UNSUPPORTED_CARD);
}
if ((ucs->ucs_prot & prots) == 0) {
return (SCARD_E_PROTO_MISMATCH);
}
return (0);
}
LONG
SCardConnect(SCARDCONTEXT hdl, LPCSTR reader, DWORD mode, DWORD prots,
LPSCARDHANDLE iccp, LPDWORD protp)
{
LONG ret;
uccid_cmd_status_t ucs;
pcsc_card_t *card;
if (!pcsc_valid_context(hdl)) {
return (SCARD_E_INVALID_HANDLE);
}
if (reader == NULL) {
return (SCARD_E_UNKNOWN_READER);
}
if (iccp == NULL || protp == NULL) {
return (SCARD_E_INVALID_PARAMETER);
}
if (mode != SCARD_SHARE_SHARED) {
return (SCARD_E_INVALID_VALUE);
}
if ((prots & ~(SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1 |
SCARD_PROTOCOL_RAW | SCARD_PROTOCOL_T15)) != 0) {
return (SCARD_E_INVALID_VALUE);
}
if ((prots & (SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1)) == 0) {
return (SCARD_E_UNSUPPORTED_FEATURE);
}
if ((card = pcsc_card_alloc(hdl, reader)) == NULL) {
pcsc_card_free(card);
return (SCARD_E_NO_MEMORY);
}
if ((card->pcc_fd = open(reader, O_RDWR)) < 0) {
pcsc_card_free(card);
switch (errno) {
case ENOENT:
return (SCARD_E_UNKNOWN_READER);
default:
return (SCARD_F_UNKNOWN_ERROR);
}
}
if ((ret = uccid_status_helper(card->pcc_fd, prots, &ucs)) != 0) {
pcsc_card_free(card);
return (ret);
}
*protp = ucs.ucs_prot;
*iccp = card;
return (SCARD_S_SUCCESS);
}
LONG
SCardStatus(SCARDHANDLE arg, LPSTR readerp, LPDWORD readerlenp,
LPDWORD statep, LPDWORD protop, LPBYTE atrp, LPDWORD atrlenp)
{
pcsc_card_t *card = arg;
pcsc_hdl_t *hdl = card->pcc_hdl;
LONG ret = SCARD_S_SUCCESS;
if (statep == NULL && protop == NULL && atrlenp == NULL) {
goto name;
}
uccid_cmd_status_t ucs = { .ucs_version = UCCID_CURRENT_VERSION };
if (ioctl(card->pcc_fd, UCCID_CMD_STATUS, &ucs) != 0) {
VERIFY3S(errno, ==, ENODEV);
ret = SCARD_E_READER_UNAVAILABLE;
goto out;
}
if (statep != NULL) {
if (!(ucs.ucs_status & UCCID_STATUS_F_CARD_PRESENT)) {
*statep = SCARD_ABSENT;
} else if (ucs.ucs_status & UCCID_STATUS_F_CARD_ACTIVE) {
if (ucs.ucs_status & UCCID_STATUS_F_PARAMS_VALID) {
*statep = SCARD_SPECIFIC;
} else {
*statep = SCARD_POWERED;
}
} else {
*statep = SCARD_PRESENT;
}
}
if (protop != NULL) {
if (ucs.ucs_status & UCCID_STATUS_F_PARAMS_VALID) {
switch (ucs.ucs_prot) {
case UCCID_PROT_T0:
*protop = SCARD_PROTOCOL_T0;
break;
case UCCID_PROT_T1:
*protop = SCARD_PROTOCOL_T1;
break;
default:
*protop = SCARD_PROTOCOL_UNDEFINED;
break;
}
} else {
*protop = SCARD_PROTOCOL_UNDEFINED;
}
}
if (atrlenp != NULL) {
uint8_t *ubuf;
uint32_t len = *atrlenp;
if (len != SCARD_AUTOALLOCATE) {
if (len < ucs.ucs_atrlen) {
*atrlenp = ucs.ucs_atrlen;
ret = SCARD_E_INSUFFICIENT_BUFFER;
goto out;
}
if (atrp == NULL) {
ret = SCARD_E_INVALID_PARAMETER;
goto out;
}
ubuf = atrp;
} else {
if ((ubuf = pcsc_mem_alloc(hdl, ucs.ucs_atrlen)) ==
NULL) {
ret = SCARD_E_NO_MEMORY;
goto out;
}
*((LPBYTE *)atrp) = ubuf;
}
bcopy(ucs.ucs_atr, ubuf, ucs.ucs_atrlen);
*atrlenp = ucs.ucs_atrlen;
}
name:
if (readerlenp != NULL) {
char *ubuf;
uint32_t rlen = *readerlenp;
if (rlen != SCARD_AUTOALLOCATE) {
if (rlen < card->pcc_namelen) {
*readerlenp = card->pcc_namelen;
ret = SCARD_E_INSUFFICIENT_BUFFER;
goto out;
}
if (readerp == NULL) {
ret = SCARD_E_INVALID_PARAMETER;
goto out;
}
ubuf = readerp;
} else {
if ((ubuf = pcsc_mem_alloc(hdl, card->pcc_namelen)) ==
NULL) {
ret = SCARD_E_NO_MEMORY;
goto out;
}
*((LPSTR *)readerp) = ubuf;
}
bcopy(card->pcc_name, ubuf, card->pcc_namelen);
}
out:
return (ret);
}
LONG
SCardDisconnect(SCARDHANDLE arg, DWORD disposition)
{
pcsc_card_t *card = arg;
if (arg == NULL) {
return (SCARD_E_INVALID_HANDLE);
}
switch (disposition) {
case SCARD_RESET_CARD: {
uccid_cmd_txn_begin_t txnbegin = {
.uct_version = UCCID_CURRENT_VERSION,
};
if (ioctl(card->pcc_fd, UCCID_CMD_TXN_BEGIN, &txnbegin) != 0) {
VERIFY3S(errno, !=, EFAULT);
switch (errno) {
case ENODEV:
goto close;
case EEXIST:
break;
case EBUSY:
return (SCARD_E_SHARING_VIOLATION);
default:
return (SCARD_F_UNKNOWN_ERROR);
}
}
uccid_cmd_txn_end_t txnend = {
.uct_version = UCCID_CURRENT_VERSION,
.uct_flags = UCCID_TXN_END_RESET,
};
if (ioctl(card->pcc_fd, UCCID_CMD_TXN_END, &txnend) != 0) {
VERIFY3S(errno, !=, EFAULT);
switch (errno) {
case ENODEV:
goto close;
default:
return (SCARD_F_UNKNOWN_ERROR);
}
}
}
case SCARD_LEAVE_CARD:
break;
default:
return (SCARD_E_INVALID_VALUE);
}
close:
if (close(card->pcc_fd) != 0) {
return (SCARD_F_UNKNOWN_ERROR);
}
card->pcc_fd = -1;
pcsc_card_free(card);
return (SCARD_S_SUCCESS);
}
LONG
SCardBeginTransaction(SCARDHANDLE arg)
{
uccid_cmd_txn_begin_t txn;
pcsc_card_t *card = arg;
if (card == NULL) {
return (SCARD_E_INVALID_HANDLE);
}
bzero(&txn, sizeof (uccid_cmd_txn_begin_t));
txn.uct_version = UCCID_CURRENT_VERSION;
if (ioctl(card->pcc_fd, UCCID_CMD_TXN_BEGIN, &txn) != 0) {
VERIFY3S(errno, !=, EFAULT);
switch (errno) {
case ENODEV:
return (SCARD_E_READER_UNAVAILABLE);
case EEXIST:
return (SCARD_S_SUCCESS);
case EBUSY:
return (SCARD_E_SHARING_VIOLATION);
case EINPROGRESS:
case EINTR:
default:
return (SCARD_F_UNKNOWN_ERROR);
}
}
return (SCARD_S_SUCCESS);
}
LONG
SCardEndTransaction(SCARDHANDLE arg, DWORD state)
{
uccid_cmd_txn_end_t txn;
pcsc_card_t *card = arg;
if (card == NULL) {
return (SCARD_E_INVALID_HANDLE);
}
bzero(&txn, sizeof (uccid_cmd_txn_end_t));
txn.uct_version = UCCID_CURRENT_VERSION;
switch (state) {
case SCARD_LEAVE_CARD:
txn.uct_flags = UCCID_TXN_END_RELEASE;
break;
case SCARD_RESET_CARD:
txn.uct_flags = UCCID_TXN_END_RESET;
break;
case SCARD_UNPOWER_CARD:
case SCARD_EJECT_CARD:
default:
return (SCARD_E_INVALID_VALUE);
}
if (ioctl(card->pcc_fd, UCCID_CMD_TXN_END, &txn) != 0) {
VERIFY3S(errno, !=, EFAULT);
switch (errno) {
case ENODEV:
return (SCARD_E_READER_UNAVAILABLE);
case ENXIO:
return (SCARD_E_SHARING_VIOLATION);
default:
return (SCARD_F_UNKNOWN_ERROR);
}
}
return (SCARD_S_SUCCESS);
}
LONG
SCardReconnect(SCARDHANDLE arg, DWORD mode, DWORD prots, DWORD init,
LPDWORD protp)
{
uccid_cmd_status_t ucs;
pcsc_card_t *card = arg;
LONG ret;
if (card == NULL) {
return (SCARD_E_INVALID_HANDLE);
}
if (protp == NULL) {
return (SCARD_E_INVALID_PARAMETER);
}
if (mode != SCARD_SHARE_SHARED) {
return (SCARD_E_INVALID_VALUE);
}
if ((prots & ~(SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1 |
SCARD_PROTOCOL_RAW | SCARD_PROTOCOL_T15)) != 0) {
return (SCARD_E_INVALID_VALUE);
}
if ((prots & (SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1)) == 0) {
return (SCARD_E_UNSUPPORTED_FEATURE);
}
if (init != SCARD_LEAVE_CARD) {
return (SCARD_E_INVALID_VALUE);
}
if ((ret = uccid_status_helper(card->pcc_fd, prots, &ucs)) != 0)
return (ret);
*protp = ucs.ucs_prot;
return (SCARD_S_SUCCESS);
}
LONG
SCardTransmit(SCARDHANDLE arg, const SCARD_IO_REQUEST *sendreq,
LPCBYTE sendbuf, DWORD sendlen, SCARD_IO_REQUEST *recvreq, LPBYTE recvbuf,
LPDWORD recvlenp)
{
int len;
ssize_t ret;
pcsc_card_t *card = arg;
if (card == NULL) {
return (SCARD_E_INVALID_HANDLE);
}
if (sendbuf == NULL || recvbuf == NULL || recvlenp == NULL) {
return (SCARD_E_INVALID_PARAMETER);
}
ret = write(card->pcc_fd, sendbuf, sendlen);
if (ret == -1) {
switch (errno) {
case E2BIG:
return (SCARD_E_INVALID_PARAMETER);
case ENODEV:
return (SCARD_E_READER_UNAVAILABLE);
case EACCES:
case EBUSY:
return (SCARD_E_SHARING_VIOLATION);
case ENXIO:
return (SCARD_W_REMOVED_CARD);
case EFAULT:
return (SCARD_E_INVALID_PARAMETER);
case ENOMEM:
default:
return (SCARD_F_UNKNOWN_ERROR);
}
}
ASSERT3S(ret, ==, sendlen);
ret = read(card->pcc_fd, recvbuf, *recvlenp);
if (ret == -1) {
switch (errno) {
case EINVAL:
case EOVERFLOW:
if (ioctl(card->pcc_fd, FIONREAD, &len) != 0) {
return (SCARD_F_UNKNOWN_ERROR);
}
*recvlenp = len;
return (SCARD_E_INSUFFICIENT_BUFFER);
case ENODEV:
return (SCARD_E_READER_UNAVAILABLE);
case EACCES:
case EBUSY:
return (SCARD_E_SHARING_VIOLATION);
case EFAULT:
return (SCARD_E_INVALID_PARAMETER);
case ENODATA:
default:
return (SCARD_F_UNKNOWN_ERROR);
}
}
*recvlenp = ret;
return (SCARD_S_SUCCESS);
}