diff options
Diffstat (limited to 'usr/src/lib/libpcsc/common/libpcsc.c')
-rw-r--r-- | usr/src/lib/libpcsc/common/libpcsc.c | 615 |
1 files changed, 615 insertions, 0 deletions
diff --git a/usr/src/lib/libpcsc/common/libpcsc.c b/usr/src/lib/libpcsc/common/libpcsc.c new file mode 100644 index 0000000000..41d646e8b1 --- /dev/null +++ b/usr/src/lib/libpcsc/common/libpcsc.c @@ -0,0 +1,615 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2019, Joyent, Inc. + */ + +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <fts.h> +#include <errno.h> +#include <strings.h> +#include <unistd.h> +#include <sys/debug.h> +#include <sys/filio.h> +#include <sys/usb/clients/ccid/uccid.h> + +#include <winscard.h> + +/* + * Implementation of the PCSC library leveraging the uccid framework. + */ + +/* + * The library handle is basically unused today. We keep this around such that + * consumers which expect to receive a non-NULL opaque handle have something + * they can use. + */ +typedef struct pcsc_hdl { + hrtime_t pcsc_create_time; +} pcsc_hdl_t; + +typedef struct pcsc_card { + int pcc_fd; +} pcsc_card_t; + +/* + * Required globals + */ +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"); + } +} + + +/* + * This is called when a caller wishes to open a new Library context. + */ +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); + } + + hdl->pcsc_create_time = gethrtime(); + *outp = hdl; + return (SCARD_S_SUCCESS); +} + +/* + * This is called to free a library context from a client. + */ +LONG +SCardReleaseContext(SCARDCONTEXT hdl) +{ + free(hdl); + return (SCARD_S_SUCCESS); +} + +/* + * This is called to release memory allocated by the library. No, it doesn't + * make sense to take a const pointer when being given memory to free. It just + * means we have to cast it, but remember: this isn't our API. + */ +LONG +SCardFreeMemory(SCARDCONTEXT unused, LPCVOID mem) +{ + free((void *)mem); + return (SCARD_S_SUCCESS); +} + +/* + * This is called by a caller to get a list of readers that exist in the system. + * If lenp is set to SCARD_AUTOALLOCATE, then we are responsible for dealing + * with this memory. + */ +LONG +SCardListReaders(SCARDCONTEXT unused, LPCSTR groups, LPSTR bufp, LPDWORD lenp) +{ + 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 (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; + /* + * Account for the NUL we'll have to place at the end of this. + */ + 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) { + /* + * I mean, it's true. But I wish I could just give you + * EOVERFLOW. + */ + 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; + } + + ubuf = malloc(ulen); + if (ubuf == 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) +{ + /* + * Get the status of this slot and find out information about the slot. + * We need to see if there's an ICC present and if it matches the + * current protocol. If not, then we have to fail this. + */ + 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 (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 = malloc(sizeof (*card))) == NULL) { + return (SCARD_E_NO_MEMORY); + } + + if ((card->pcc_fd = open(reader, O_RDWR)) < 0) { + 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) + goto cleanup; + + *protp = ucs.ucs_prot; + *iccp = card; + return (SCARD_S_SUCCESS); +cleanup: + (void) close(card->pcc_fd); + free(card); + return (ret); +} + +LONG +SCardDisconnect(SCARDHANDLE arg, DWORD disposition) +{ + pcsc_card_t *card = arg; + + if (arg == NULL) { + return (SCARD_E_INVALID_HANDLE); + } + + if (disposition != SCARD_LEAVE_CARD) { + return (SCARD_E_INVALID_VALUE); + } + + if (close(card->pcc_fd) != 0) { + return (SCARD_F_UNKNOWN_ERROR); + } + + 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); + } + + /* + * The semantics of pcsc are that this operation does not block, but + * instead fails if we cannot grab it immediately. + */ + bzero(&txn, sizeof (uccid_cmd_txn_begin_t)); + txn.uct_version = UCCID_CURRENT_VERSION; + txn.uct_flags = UCCID_TXN_DONT_BLOCK; + + 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: + /* + * This is an odd case. It's used to tell us that we + * already have it. For now, just treat it as success. + */ + return (SCARD_S_SUCCESS); + case EBUSY: + return (SCARD_E_SHARING_VIOLATION); + /* + * EINPROGRESS is a weird case. It means that we were trying to + * grab a hold while another instance using the same handle was. + * For now, treat it as an unknown error. + */ + 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); + } + + /* + * Ignore sendreq / recvreq. + */ + if (sendbuf == NULL || recvbuf == NULL || recvlenp == NULL) { + return (SCARD_E_INVALID_PARAMETER); + } + + /* + * The CCID write will always consume all data or none. + */ + 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); + + /* + * Now, we should be able to block in read. + */ + ret = read(card->pcc_fd, recvbuf, *recvlenp); + if (ret == -1) { + switch (errno) { + case EINVAL: + case EOVERFLOW: + /* + * This means that we need to update len with the real + * one. + */ + 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); +} |