[Spice-devel] [xf86-video-qxl v6] Enable smartcard support for XSpice.

Jeremy White jwhite at codeweavers.com
Mon Dec 15 14:24:45 PST 2014


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 at codeweavers.com>
---
 configure.ac                           |   25 ++
 examples/spiceqxl.xorg.conf.example    |    3 +
 src/Makefile.am                        |    3 +-
 src/qxl.h                              |    2 +
 src/qxl_driver.c                       |   14 +-
 src/spiceccid/Makefile.am              |   29 ++
 src/spiceccid/spice.pcsc.conf.template |    7 +
 src/spiceccid/spiceccid.c              |  477 ++++++++++++++++++++++++++++++++
 src/spiceqxl_smartcard.c               |  193 +++++++++++++
 src/spiceqxl_smartcard.h               |   31 +++
 10 files changed, 782 insertions(+), 2 deletions(-)
 create mode 100644 src/spiceccid/Makefile.am
 create mode 100644 src/spiceccid/spice.pcsc.conf.template
 create mode 100644 src/spiceccid/spiceccid.c
 create mode 100644 src/spiceqxl_smartcard.c
 create mode 100644 src/spiceqxl_smartcard.h

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 at 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
-- 
1.7.10.4



More information about the Spice-devel mailing list