summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy White <jwhite@codeweavers.com>2014-12-15 16:24:45 -0600
committerJeremy White <jwhite@codeweavers.com>2015-01-08 08:04:29 -0600
commitcd96284cf17524a3618ae542d53fa3c987e85a75 (patch)
treee30d37408740cccb310876c77d596bbecd240a99
parent9368451e74c77b3d04383b5b98a1d71e6f12eb55 (diff)
Enable smartcard support for XSpice.
This is done by creating a Unix domain socket to which smartcard messages are transferred, using the vscard protocol. A further system library, spiceccid, is used to provide an interface into pcsc-lite, specifically the pcsc-lite daemon, so that regular Unix applications can access the passed through smartcard information. Signed-off-by: Jeremy White <jwhite@codeweavers.com>
-rw-r--r--configure.ac25
-rw-r--r--examples/spiceqxl.xorg.conf.example3
-rw-r--r--src/Makefile.am3
-rw-r--r--src/qxl.h2
-rw-r--r--src/qxl_driver.c14
-rw-r--r--src/spiceccid/Makefile.am29
-rw-r--r--src/spiceccid/spice.pcsc.conf.template7
-rw-r--r--src/spiceccid/spiceccid.c477
-rw-r--r--src/spiceqxl_smartcard.c193
-rw-r--r--src/spiceqxl_smartcard.h31
10 files changed, 782 insertions, 2 deletions
diff --git a/configure.ac b/configure.ac
index 14e0597..27b1cc7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -137,8 +137,31 @@ if test "x$enable_xspice" = "xyes"; then
else
enable_xspice=no
fi
+
+AC_ARG_ENABLE([ccid],
+ [AS_HELP_STRING([--enable-ccid],
+ [Build the spiceccid SmartCard driver (default is no)])],
+ [enable_ccid=$enableval],
+ [enable_ccid=no])
+AC_ARG_WITH(ccid-module-dir,
+ [AS_HELP_STRING([--with-ccid-module-dir=DIR ],
+ [Specify the install path for spiceccid driver (default is $libdir/pcsc/drivers/serial)])],
+ [ cciddir="$withval" ],
+ [ cciddir="$libdir/pcsc/drivers/serial" ])
+AC_SUBST(cciddir)
+if test "x$enable_ccid" != "xno"; then
+ PKG_CHECK_MODULES(LIBPCSCLITE, [libpcsclite])
+ PKG_CHECK_MODULES(LIBCACARD, [libcacard])
+
+ if test "x$enable_xspice" = "xno"; then
+ AC_MSG_ERROR([Building with ccid requires xspice, but xspice is not enabled])
+ fi
+fi
+
+
AM_CONDITIONAL(BUILD_XSPICE, test "x$enable_xspice" = "xyes")
AM_CONDITIONAL(BUILD_QXL, test "x$enable_qxl" = "xyes")
+AM_CONDITIONAL(BUILD_SPICECCID, test "x$enable_ccid" = "xyes")
AC_ARG_ENABLE([udev],
AS_HELP_STRING([--disable-udev], [Disable libudev support [default=auto]]),
@@ -168,6 +191,7 @@ fi
AC_CONFIG_FILES([
Makefile
src/Makefile
+ src/spiceccid/Makefile
src/uxa/Makefile
scripts/Makefile
examples/Makefile
@@ -187,4 +211,5 @@ echo "
KMS: ${DRM_MODE}
Build qxl: ${enable_qxl}
Build xspice: ${enable_xspice}
+ Build spiceccid: ${enable_ccid}
"
diff --git a/examples/spiceqxl.xorg.conf.example b/examples/spiceqxl.xorg.conf.example
index 597a5bd..d15f7f2 100644
--- a/examples/spiceqxl.xorg.conf.example
+++ b/examples/spiceqxl.xorg.conf.example
@@ -143,6 +143,9 @@ Section "Device"
# to the client. Default is no mixing.
#Option "SpicePlaybackFIFODir" "/tmp/"
+ # A unix domain name for a unix domain socket
+ # to communicate with a spiceccid smartcard driver
+ #Option "SpiceSmartCardFile" "/tmp/spice.pcsc.comm"
EndSection
Section "InputDevice"
diff --git a/src/Makefile.am b/src/Makefile.am
index bf50ae1..6c72bbd 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -25,7 +25,7 @@
# _ladir passes a dummy rpath to libtool so the thing will actually link
# TODO: -nostdlib/-Bstatic/-lgcc platform magic, not installing the .a, etc.
-SUBDIRS=uxa
+SUBDIRS=uxa spiceccid
AM_CFLAGS = $(SPICE_PROTOCOL_CFLAGS) $(XORG_CFLAGS) $(PCIACCESS_CFLAGS) $(CWARNFLAGS) $(DRM_CFLAGS) @LIBUDEV_CFLAGS@
@@ -96,6 +96,7 @@ spiceqxl_drv_la_SOURCES = \
spiceqxl_uinput.c \
spiceqxl_uinput.h \
spiceqxl_audio.c \
+ spiceqxl_smartcard.c \
spiceqxl_audio.h \
spiceqxl_inputs.c \
spiceqxl_inputs.h \
diff --git a/src/qxl.h b/src/qxl.h
index 603faca..54995cf 100644
--- a/src/qxl.h
+++ b/src/qxl.h
@@ -157,6 +157,7 @@ enum {
OPTION_FRAME_BUFFER_SIZE,
OPTION_SURFACE_BUFFER_SIZE,
OPTION_COMMAND_BUFFER_SIZE,
+ OPTION_SPICE_SMARTCARD_FILE,
#endif
OPTION_COUNT,
};
@@ -352,6 +353,7 @@ struct _qxl_screen_t
char playback_fifo_dir[PATH_MAX];
void *playback_opaque;
+ char smartcard_file[PATH_MAX];
#endif /* XSPICE */
uint32_t deferred_fps;
diff --git a/src/qxl_driver.c b/src/qxl_driver.c
index 165f468..9ad8921 100644
--- a/src/qxl_driver.c
+++ b/src/qxl_driver.c
@@ -55,6 +55,7 @@
#include "spiceqxl_io_port.h"
#include "spiceqxl_spice_server.h"
#include "spiceqxl_audio.h"
+#include "spiceqxl_smartcard.h"
#include "spiceqxl_vdagent.h"
#endif /* XSPICE */
@@ -152,8 +153,10 @@ const OptionInfoRec DefaultOptions[] =
"SurfaceBufferSize", OPTV_INTEGER, {DEFAULT_SURFACE_BUFFER_SIZE}, FALSE},
{ OPTION_COMMAND_BUFFER_SIZE,
"CommandBufferSize", OPTV_INTEGER, {DEFAULT_COMMAND_BUFFER_SIZE}, FALSE},
+ { OPTION_SPICE_SMARTCARD_FILE,
+ "SpiceSmartcardFile", OPTV_STRING, {0}, FALSE},
#endif
-
+
{ -1, NULL, OPTV_NONE, {0}, FALSE }
};
@@ -659,6 +662,7 @@ spiceqxl_screen_init (ScrnInfoPtr pScrn, qxl_screen_t *qxl)
}
qxl_add_spice_display_interface (qxl);
qxl_add_spice_playback_interface (qxl);
+ qxl_add_spice_smartcard_interface (qxl);
spiceqxl_vdagent_init (qxl);
}
else
@@ -1034,6 +1038,7 @@ qxl_pre_init (ScrnInfoPtr pScrn, int flags)
unsigned int max_x, max_y;
#ifdef XSPICE
const char *playback_fifo_dir;
+ const char *smartcard_file;
#endif
/* In X server 1.7.5, Xorg -configure will cause this
@@ -1089,6 +1094,13 @@ qxl_pre_init (ScrnInfoPtr pScrn, int flags)
else
qxl->playback_fifo_dir[0] = '\0';
+ smartcard_file = get_str_option(qxl->options, OPTION_SPICE_SMARTCARD_FILE,
+ "XSPICE_SMARTCARD_FILE");
+ if (smartcard_file)
+ strncpy(qxl->smartcard_file, smartcard_file, sizeof(qxl->smartcard_file));
+ else
+ qxl->smartcard_file[0] = '\0';
+
qxl->surface0_size =
get_int_option (qxl->options, OPTION_FRAME_BUFFER_SIZE, "QXL_FRAME_BUFFER_SIZE") << 20L;
qxl->vram_size =
diff --git a/src/spiceccid/Makefile.am b/src/spiceccid/Makefile.am
new file mode 100644
index 0000000..437e992
--- /dev/null
+++ b/src/spiceccid/Makefile.am
@@ -0,0 +1,29 @@
+# Copyright 2014 Jeremy White for CodeWeavers, Inc.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# on the rights to use, copy, modify, merge, publish, distribute, sub
+# license, and/or sell copies of the Software, and to permit persons to whom
+# the Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice (including the next
+# paragraph) shall be included in all copies or substantial portions of the
+# Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+AM_CFLAGS = $(LIBPCSCLITE_CFLAGS)
+
+if BUILD_SPICECCID
+libspiceccid_la_LTLIBRARIES = libspiceccid.la
+libspiceccid_la_LDFLAGS = $(LIBPCSCLITE_LDFLAGS)
+libspiceccid_la_SOURCES = spiceccid.c
+libspiceccid_ladir = @cciddir@/
+endif
diff --git a/src/spiceccid/spice.pcsc.conf.template b/src/spiceccid/spice.pcsc.conf.template
new file mode 100644
index 0000000..345cdf5
--- /dev/null
+++ b/src/spiceccid/spice.pcsc.conf.template
@@ -0,0 +1,7 @@
+# Spice CCID Reader
+# This configuration file is the format required by the pcscd deamon for
+# serial devices; so the qxl driver looks like a serial device to pcscd.
+FRIENDLYNAME "Spice ccid"
+DEVICENAME /tmp/spice.pcsc.comm
+LIBPATH /usr/lib/pcsc/drivers/serial/libspiceccid.so
+CHANNELID 1
diff --git a/src/spiceccid/spiceccid.c b/src/spiceccid/spiceccid.c
new file mode 100644
index 0000000..9f630d2
--- /dev/null
+++ b/src/spiceccid/spiceccid.c
@@ -0,0 +1,477 @@
+/*
+ * Copyright (C) 2014 CodeWeavers, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Authors:
+ * Jeremy White <jwhite@codeweavers.com>
+ */
+
+/*----------------------------------------------------------------------------
+ Chip/Smart Card Interface Devices driver for Spice
+
+ This driver is built to interface to pcsc-lite as a serial smartcard
+ device.
+ It translates the IFD (Interface device) ABI into the Spice protocol.
+----------------------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "cacard/vscard_common.h"
+#include "ifdhandler.h"
+#include <arpa/inet.h>
+
+typedef struct apdu_list {
+ void *data;
+ int len;
+ struct apdu_list *next;
+} apdu_t;
+
+#define MAX_LUNS 2
+typedef struct smartcard_ccid {
+ int fd;
+ int lun;
+ pthread_t tid;
+ int state;
+ char atr[36];
+ int atr_len;
+ pthread_mutex_t apdu_lock;
+ apdu_t *apdu_list;
+} smartcard_ccid_t;
+
+#define STATE_OPEN 1
+#define STATE_READER_ADDED 2
+#define STATE_READER_REMOVED 4
+
+#if ! defined(MIN)
+#define MIN(x, y) (((x) < (y)) ? (x) : (y))
+#endif
+
+
+smartcard_ccid_t luns[MAX_LUNS] = { { -1 }, { -1 } };
+
+RESPONSECODE IFDHCloseChannel(DWORD Lun);
+
+static void push_apdu(smartcard_ccid_t *ccid, void *data, int len)
+{
+ apdu_t *a = malloc(sizeof(*a));
+ apdu_t **p;
+
+ a->data = malloc(len);
+ a->len = len;
+ a->next = NULL;
+ memcpy(a->data, data, len);
+
+ pthread_mutex_lock(&ccid->apdu_lock);
+ for (p = &ccid->apdu_list; *p; p = &(*p)->next)
+ ;
+ *p = a;
+
+ pthread_mutex_unlock(&ccid->apdu_lock);
+}
+
+static apdu_t * pop_apdu(smartcard_ccid_t *ccid)
+{
+ apdu_t *p;
+ pthread_mutex_lock(&ccid->apdu_lock);
+ p = ccid->apdu_list;
+ if (ccid->apdu_list)
+ ccid->apdu_list = p->next;
+ pthread_mutex_unlock(&ccid->apdu_lock);
+ return p;
+}
+
+static void free_apdu(apdu_t *a)
+{
+ free(a->data);
+ free(a);
+}
+
+static void send_reply(smartcard_ccid_t *ccid, uint32_t code)
+{
+ uint32_t reply[4];
+
+ reply[0] = htonl(VSC_Error); // type
+ reply[1] = htonl(ccid->lun); // reader id
+ reply[2] = htonl(sizeof(uint32_t)); // length
+ reply[3] = htonl(code); // Error code
+
+ if (write(ccid->fd, (char *) reply, sizeof(reply)) != sizeof(reply)) {
+ fprintf(stderr, "Error: lun %d fd %d write failed; errno %d\n", ccid->lun, ccid->fd, errno);
+ IFDHCloseChannel(ccid->lun);
+ }
+}
+
+static int send_tx_buffer(smartcard_ccid_t *ccid, void *data, int len)
+{
+ uint32_t *reply, *p;
+ int write_len = sizeof(*reply) * 3 + len;
+
+ reply = malloc(write_len);
+ p = reply;
+
+ *p++ = htonl(VSC_APDU); // type
+ *p++ = htonl(ccid->lun); // reader id
+ *p++ = htonl(len);
+ memcpy(p, data, len);
+
+ if (write(ccid->fd, (char *) reply, write_len) != write_len) {
+ fprintf(stderr, "Error: lun %d fd %d write failed; errno %d\n", ccid->lun, ccid->fd, errno);
+ IFDHCloseChannel(ccid->lun);
+ free(reply);
+ return 0;
+ }
+ free(reply);
+ return 1;
+}
+
+static void process_reader_add(smartcard_ccid_t *ccid, VSCMsgHeader *h, char *data)
+{
+ if (ccid->state & STATE_READER_ADDED) {
+ send_reply(ccid, VSC_GENERAL_ERROR);
+ return;
+ }
+
+ ccid->state |= STATE_READER_ADDED;
+ ccid->state &= ~STATE_READER_REMOVED;
+
+ pthread_mutex_init(&ccid->apdu_lock, NULL);
+ ccid->apdu_list = NULL;
+
+ send_reply(ccid, VSC_SUCCESS);
+}
+
+static void process_reader_remove(smartcard_ccid_t *ccid, VSCMsgHeader *h)
+{
+ apdu_t *p;
+
+ if (ccid->state & STATE_READER_REMOVED) {
+ send_reply(ccid, VSC_GENERAL_ERROR);
+ return;
+ }
+
+ ccid->state |= STATE_READER_REMOVED;
+ ccid->state &= ~STATE_READER_ADDED;
+
+ while (p = pop_apdu(ccid))
+ free_apdu(p);
+
+ pthread_mutex_destroy(&ccid->apdu_lock);
+
+ send_reply(ccid, VSC_SUCCESS);
+}
+
+static void process_atr(smartcard_ccid_t *ccid, VSCMsgHeader *h, char *data)
+{
+ ccid->atr_len = h->length;
+ if (h->length > 0 && h->length > sizeof(ccid->atr)) {
+ fprintf(stderr, "Supplied ATR of length %d exceeds %d maximum\n",
+ h->length, sizeof(ccid->atr));
+ send_reply(ccid, VSC_GENERAL_ERROR);
+ return;
+ }
+
+ memset(ccid->atr, 0, sizeof(ccid->atr));
+ memcpy(ccid->atr, data, ccid->atr_len);
+
+ send_reply(ccid, VSC_SUCCESS);
+}
+
+static void process_apdu(smartcard_ccid_t *ccid, VSCMsgHeader *h, char *data)
+{
+ if (ccid->state & STATE_READER_ADDED)
+ push_apdu(ccid, data, h->length);
+}
+
+static void process_card_remove(smartcard_ccid_t *ccid, VSCMsgHeader *h)
+{
+ ccid->atr_len = 0;
+ memset(ccid->atr, 0, sizeof(ccid->atr));
+ send_reply(ccid, VSC_SUCCESS);
+}
+
+static int process_message(smartcard_ccid_t *ccid, char *buf, int len)
+{
+ VSCMsgHeader h;
+ uint32_t *p = (uint32_t *) buf;
+
+ h.type = ntohl(*p++);
+ h.reader_id = ntohl(*p++);
+ h.length = ntohl(*p++);
+
+ if (len < sizeof(h) || len < sizeof(h) + h.length)
+ return 0;
+
+ switch (h.type) {
+ case VSC_ReaderAdd:
+ process_reader_add(ccid, &h, h.length > 0 ? buf + sizeof(h) : NULL);
+ break;
+
+ case VSC_ReaderRemove:
+ process_reader_remove(ccid, &h);
+ break;
+
+ case VSC_ATR:
+ process_atr(ccid, &h, h.length > 0 ? buf + sizeof(h) : NULL);
+ break;
+
+ case VSC_CardRemove:
+ process_card_remove(ccid, &h);
+ break;
+
+ case VSC_APDU:
+ process_apdu(ccid, &h, h.length > 0 ? buf + sizeof(h) : NULL);
+ break;
+
+ default:
+ fprintf(stderr, "spiceccid %s: unknown smartcard message %d / %d\n", __FUNCTION__, h.type, sizeof(h) + h.length);
+
+ }
+
+ return(h.length + sizeof(h));
+}
+
+static void * lun_thread(void *arg)
+{
+ char buf[8096];
+ int pos = 0;
+ smartcard_ccid_t *ccid = (smartcard_ccid_t *) arg;
+ int rc;
+
+ while (1) {
+ rc = read(ccid->fd, buf + pos, sizeof(buf) - pos);
+ if (rc == -1)
+ if (errno == EINTR)
+ continue;
+ else
+ break;
+
+ if (rc == 0)
+ break;
+
+ pos += rc;
+
+ do {
+ rc = process_message(ccid, buf, pos);
+ pos -= rc;
+
+ if (rc > 0 && pos > 0)
+ memmove(buf, buf + rc, pos);
+ } while (rc > 0 && pos > 0);
+ }
+
+ return NULL;
+}
+
+
+static void send_init(smartcard_ccid_t *ccid)
+{
+ uint32_t msg[6];
+
+ msg[0] = htonl(VSC_Init); // type
+ msg[1] = htonl(ccid->lun); // reader id
+ msg[2] = htonl(sizeof(uint32_t) * 3); // length
+ msg[3] = htonl(VSCARD_MAGIC); // VSCD
+ msg[4] = htonl(VSCARD_VERSION); // VSCD
+ msg[5] = 0; // capabilities
+
+ if (write(ccid->fd, (char *) msg, sizeof(msg)) != sizeof(msg)) {
+ fprintf(stderr, "Error: lun %d fd %d write failed; errno %d\n", ccid->lun, ccid->fd, errno);
+ IFDHCloseChannel(ccid->lun);
+ }
+}
+
+/*----------------------------------------------------------------------------
+ IFDHCreateChannelByName
+ The pcsc daemon should invoke this function passing in the path name
+ configured in reader.conf.
+*/
+RESPONSECODE IFDHCreateChannelByName(DWORD Lun, LPSTR DeviceName)
+{
+ int i;
+ struct sockaddr_un addr;
+
+ for (i = 0; i < MAX_LUNS; i++)
+ if (luns[i].fd != -1 && luns[i].lun == Lun)
+ return IFD_COMMUNICATION_ERROR;
+
+ for (i = 0; i < MAX_LUNS; i++)
+ if (luns[i].fd == -1)
+ break;
+
+ if (i >= MAX_LUNS)
+ return IFD_COMMUNICATION_ERROR;
+
+ luns[i].fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (luns[i].fd < 0)
+ return IFD_NO_SUCH_DEVICE;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, DeviceName, sizeof(addr.sun_path) - 1);
+ if (connect(luns[i].fd, (struct sockaddr *) &addr, sizeof(addr))) {
+ close(luns[i].fd);
+ return IFD_COMMUNICATION_ERROR;
+ }
+
+ if (pthread_create(&luns[i].tid, NULL, &lun_thread, &luns[i])) {
+ close(luns[i].fd);
+ return IFD_COMMUNICATION_ERROR;
+ }
+
+ luns[i].lun = Lun;
+ luns[i].state = STATE_OPEN;
+
+ return IFD_SUCCESS;
+}
+
+RESPONSECODE IFDHCreateChannel(DWORD Lun, DWORD Channel)
+{
+ fprintf(stderr, "spiceccid %s unsupported: Lun %ld, Channel %ld\n", __FUNCTION__, Lun, Channel);
+ return IFD_ERROR_NOT_SUPPORTED;
+}
+
+RESPONSECODE IFDHCloseChannel(DWORD Lun)
+{
+ int i;
+
+ for (i = 0; i < MAX_LUNS; i++) {
+ if (luns[i].fd != -1 && luns[i].lun == Lun) {
+ pthread_cancel(luns[i].tid);
+ close(luns[i].fd);
+ luns[i].fd = -1;
+ luns[i].lun = 0;
+ luns[i].atr_len = 0;
+ luns[i].state &= ~STATE_OPEN;
+ break;
+ }
+ }
+
+ if (i == MAX_LUNS)
+ return IFD_NO_SUCH_DEVICE;
+
+ return IFD_SUCCESS;
+}
+
+RESPONSECODE IFDHGetCapabilities(DWORD Lun, DWORD Tag, PDWORD Length, PUCHAR Value)
+{
+ fprintf(stderr, "spiceccid %s unsupported: Lun %ld, Tag %ld, Length %ld, Value %p\n", __FUNCTION__, Lun, Tag, *Length, Value);
+ /* TODO - explore supporting TAG_IFD_POLLING_THREAD */
+ return IFD_ERROR_NOT_SUPPORTED;
+}
+
+RESPONSECODE IFDHSetCapabilities(DWORD Lun, DWORD Tag, DWORD Length, PUCHAR Value)
+{
+ return IFD_ERROR_NOT_SUPPORTED;
+}
+
+RESPONSECODE IFDHPowerICC(DWORD Lun, DWORD Action, PUCHAR Atr, PDWORD AtrLength)
+{
+ int i;
+
+ for (i = 0; i < MAX_LUNS; i++)
+ if (luns[i].fd != -1 && luns[i].lun == Lun)
+ if (Action == IFD_POWER_UP) {
+ if (*AtrLength >= luns[i].atr_len) {
+ memcpy(Atr, luns[i].atr, luns[i].atr_len);
+ *AtrLength = luns[i].atr_len;
+ }
+ send_init(&luns[i]);
+ return IFD_SUCCESS;
+ }
+
+ return IFD_ERROR_NOT_SUPPORTED;
+}
+
+#define TX_MAX_SLEEP 5000
+#define TX_SLEEP_INTERVAL 1000
+RESPONSECODE IFDHTransmitToICC(DWORD Lun, SCARD_IO_HEADER SendPci,
+ PUCHAR TxBuffer, DWORD TxLength, PUCHAR RxBuffer, PDWORD
+ RxLength, PSCARD_IO_HEADER RecvPci)
+{
+ apdu_t *p;
+ int i, j;
+
+ for (i = 0; i < MAX_LUNS; i++)
+ if (luns[i].fd != -1 && luns[i].lun == Lun) {
+ while (p = pop_apdu(&luns[i]))
+ free_apdu(p);
+
+ if (send_tx_buffer(&luns[i], TxBuffer, TxLength)) {
+ for (j = 0; j < TX_MAX_SLEEP; j++)
+ if (p = pop_apdu(&luns[i]))
+ break;
+ else
+ usleep(TX_SLEEP_INTERVAL);
+
+ if (p) {
+ memcpy(RxBuffer, p->data, MIN(p->len, *RxLength));
+ *RxLength = MIN(p->len, *RxLength);
+ free_apdu(p);
+ return IFD_SUCCESS;
+ }
+
+ return IFD_RESPONSE_TIMEOUT;
+ }
+ }
+ return IFD_NO_SUCH_DEVICE;
+}
+
+RESPONSECODE IFDHICCPresence(DWORD Lun)
+{
+ int i;
+
+ for (i = 0; i < MAX_LUNS; i++)
+ if (luns[i].fd != -1 && luns[i].lun == Lun) {
+ if (luns[i].atr_len > 0 && luns[i].state & STATE_READER_ADDED)
+ return IFD_SUCCESS;
+
+ return IFD_ICC_NOT_PRESENT;
+ }
+
+ return IFD_NO_SUCH_DEVICE;
+}
+
+RESPONSECODE IFDHSetProtocolParameters(DWORD Lun, DWORD Protocol, UCHAR Flags,
+ UCHAR PTS1, UCHAR PTS2, UCHAR PTS3)
+{
+ if (Protocol == SCARD_PROTOCOL_T1)
+ return IFD_SUCCESS;
+
+ return IFD_NOT_SUPPORTED;
+}
+
+RESPONSECODE IFDHControl(DWORD Lun, DWORD dwControlCode, PUCHAR
+ TxBuffer, DWORD TxLength, PUCHAR RxBuffer, DWORD RxLength,
+ LPDWORD pdwBytesReturned)
+{
+ fprintf(stderr, "spiceccid %s unsupported: Lun %ld\n", __FUNCTION__, Lun);
+ return IFD_ERROR_NOT_SUPPORTED;
+}
diff --git a/src/spiceqxl_smartcard.c b/src/spiceqxl_smartcard.c
new file mode 100644
index 0000000..f2ec2e2
--- /dev/null
+++ b/src/spiceqxl_smartcard.c
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2014 Jeremy White for CodeWeavers Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * on the rights to use, copy, modify, merge, publish, distribute, sub
+ * license, and/or sell copies of the Software, and to permit persons to whom
+ * the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+
+#include "spiceqxl_smartcard.h"
+
+typedef struct XSpiceSmartcardCharDeviceInstance {
+ SpiceCharDeviceInstance base;
+ qxl_screen_t *qxl;
+ int listen_fd;
+ int fd;
+ SpiceWatch *listen_watch;
+ SpiceWatch *watch;
+} XSpiceSmartcardCharDeviceInstance;
+
+XSpiceSmartcardCharDeviceInstance smartcard_sin = {
+ .base = {
+ .subtype = "smartcard"
+ }
+};
+
+static int smartcard_write(SpiceCharDeviceInstance *sin, const uint8_t *buf, int len)
+{
+ int written;
+
+ if (smartcard_sin.fd == -1)
+ return 0;
+
+ written = write(smartcard_sin.fd, buf, len);
+ if (written != len)
+ ErrorF("%s: ERROR: short write to smartcard socket - TODO buffering\n", __FUNCTION__);
+
+ return written;
+}
+
+static int smartcard_read(SpiceCharDeviceInstance *sin, uint8_t *buf, int len)
+{
+ int rc;
+
+ if (smartcard_sin.fd == -1)
+ return 0;
+
+ rc = read(smartcard_sin.fd, buf, len);
+ if (rc <= 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
+ return 0;
+ }
+ ErrorF("smartcard socket died: %s\n", strerror(errno));
+
+ smartcard_sin.qxl->core->watch_remove(smartcard_sin.watch);
+ close(smartcard_sin.fd);
+ smartcard_sin.fd = -1;
+ smartcard_sin.watch = NULL;
+ }
+
+ return rc;
+}
+
+static void on_read_available(int fd, int event, void *opaque)
+{
+ spice_server_char_device_wakeup(&smartcard_sin.base);
+}
+
+static void on_accept_available(int fd, int event, void *opaque)
+{
+ qxl_screen_t *qxl = (qxl_screen_t *) opaque;
+ int flags;
+ int client_fd;
+
+ client_fd = accept(fd, NULL, NULL);
+ if (client_fd < 0)
+ return;
+
+ if (smartcard_sin.fd != -1) {
+ ErrorF("smartcard error: a new connection came in while an old one was active.\n");
+ close(client_fd);
+ return;
+ }
+
+ flags = fcntl(client_fd, F_GETFL, 0);
+ if (flags < 0)
+ flags = 0;
+ flags |= O_NONBLOCK;
+ fcntl(client_fd, F_SETFL, flags);
+
+ smartcard_sin.fd = client_fd;
+ smartcard_sin.watch = qxl->core->watch_add(smartcard_sin.fd, SPICE_WATCH_EVENT_READ, on_read_available, qxl);
+
+}
+
+
+#if SPICE_SERVER_VERSION >= 0x000c02
+static void smartcard_event(SpiceCharDeviceInstance *sin, uint8_t event)
+{
+ ErrorF("%s: unimplemented; event is %d\n", __FUNCTION__, event);
+}
+#endif
+
+static void smartcard_state(SpiceCharDeviceInstance *sin, int connected)
+{
+ ErrorF("%s: unimplemented; connected is %d\n", __FUNCTION__, connected);
+}
+
+static SpiceCharDeviceInterface smartcard_interface = {
+ .base.type = SPICE_INTERFACE_CHAR_DEVICE,
+ .base.description = "Xspice virtual channel char device",
+ .base.major_version = SPICE_INTERFACE_CHAR_DEVICE_MAJOR,
+ .base.minor_version = SPICE_INTERFACE_CHAR_DEVICE_MINOR,
+ .state = smartcard_state,
+ .write = smartcard_write,
+ .read = smartcard_read,
+#if SPICE_SERVER_VERSION >= 0x000c02
+ .event = smartcard_event,
+#endif
+};
+
+int
+qxl_add_spice_smartcard_interface (qxl_screen_t *qxl)
+{
+ int rc;
+ struct sockaddr_un addr;
+
+ if (qxl->smartcard_file[0] == 0) {
+ xf86DrvMsg(qxl->pScrn->scrnIndex, X_INFO, "smartcard: no file given, smartcard is disabled\n");
+ return 0;
+ }
+
+ smartcard_sin.fd = -1;
+ smartcard_sin.listen_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (smartcard_sin.listen_fd < 0) {
+ ErrorF("smartcard: unable to open socket: %s\n", strerror(errno));
+ return errno;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, qxl->smartcard_file, sizeof(addr.sun_path) - 1);
+ unlink(qxl->smartcard_file);
+
+ if (bind(smartcard_sin.listen_fd, (struct sockaddr *) &addr, sizeof(addr))) {
+ ErrorF("smartcard: unable to bind to unix domain %s: %s\n", qxl->smartcard_file, strerror(errno));
+ close(smartcard_sin.listen_fd);
+ return errno;
+ }
+
+ if (listen(smartcard_sin.listen_fd, 1)) {
+ ErrorF("smartcard: unable to listen to unix domain %s: %s\n", qxl->smartcard_file, strerror(errno));
+ close(smartcard_sin.listen_fd);
+ return errno;
+ }
+
+ smartcard_sin.listen_watch = qxl->core->watch_add(smartcard_sin.listen_fd, SPICE_WATCH_EVENT_READ, on_accept_available, qxl);
+
+ smartcard_sin.base.base.sif = &smartcard_interface.base;
+ smartcard_sin.qxl = qxl;
+
+ rc = spice_server_add_interface(qxl->spice_server, &smartcard_sin.base.base);
+ if (rc < 0)
+ return errno;
+
+ return 0;
+}
diff --git a/src/spiceqxl_smartcard.h b/src/spiceqxl_smartcard.h
new file mode 100644
index 0000000..62df5a8
--- /dev/null
+++ b/src/spiceqxl_smartcard.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2014 Jeremy White for CodeWeavers Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * on the rights to use, copy, modify, merge, publish, distribute, sub
+ * license, and/or sell copies of the Software, and to permit persons to whom
+ * the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef QXL_SPICE_SMARTCARD_H
+#define QXL_SPICE_SMARTCARD_H
+
+#include "qxl.h"
+#include <spice.h>
+
+int qxl_add_spice_smartcard_interface(qxl_screen_t *qxl);
+
+#endif